Event Kit framework is designed to grant developers access to data generated by both Calendar and Reminders system apps. The framework provides a set of API to manipulate the Calendar database, that same database which store all Calendar/Reminders informations of our iOS devices.
The power side of Event Kit is that it allows, not only to read your reminders and events data from within your app, but also it grants you access to manage such data, whether by deleting, adding, or editing Calendar and Reminders app informations.
In this tutorial, you gonna build a pretty neat app to manage your reminders data. Along the way, you will learn how to access the Calendar database to manipulate this set of informations in read and write.
Without further ado, as usual, let’s do this!
Before we dive in, download the starter project here.
Open up the project and take a look at the interfaces in the storyboard file. It mainly has 3 screens. The main screen will show the reminders in a table view. The two remaining screens will allow to add a new reminder to the Calendar database and edit an existing one.
As I said earlier, both reminders and events are stored and managed inside the Calendar database. Basically, you gonna query it to retrieve all your reminders.
Let’s start and extract all the reminders from your device to show them in the app.
Select the ViewController.swift file from the Project navigator view, and add the following properties declaration after the class name:
var eventStore: EKEventStore! var reminders: [EKReminder]!
The eventStore property represents a reference to the Calendar database, while the reminders property is an array that will store all the queried reminders before populating into the table view.
Also, don’t forget to import the EventKit framework at the top of the class:
import EventKit
Next, you will override the viewWillAppear method and implement the required code to fetch all the reminders saved in the Calendar database. To do so, locate the viewWillAppear method as change it as below:
override func viewWillAppear(animated: Bool) { // 1 self.eventStore = EKEventStore() self.reminders = [EKReminder]() self.eventStore.requestAccessToEntityType(EKEntityType.Reminder) { (granted: Bool, error: NSError?) -> Void in if granted{ // 2 let predicate = self.eventStore.predicateForRemindersInCalendars(nil) self.eventStore.fetchRemindersMatchingPredicate(predicate, completion: { (reminders: [EKReminder]?) -> Void in self.reminders = reminders dispatch_async(dispatch_get_main_queue()) { self.tableView.reloadData() } }) }else{ print("The app is not permitted to access reminders, make sure to grant permission in the settings and try again") } } }
Let’s explain the above code:
// 1: Before you access the reminders in the event store, you need to get the user permission. The requestAccessToEntityType API will run asynchronously and call the completion block when the request is completed. The block will pass two arguments, the first to check whether the user has given permission to the app to access the reminders, and the second will store an error object for a backtrace if something went wrong during the operation.
// 2: Once permission is granted to the app, it will fetch all reminders asynchronously and return the results in a completion block as well. At that time, you assign the reminders objects to the array property and reload the table view to populate the data.
Run the app, for the first time you should get the permission alert popping up, make sure to allow the app and grant it permission.
Cool, now let’s implement some UITableViewDataSource protocol methods in order to reload the table view with data.
Always in the ViewController.swift file, place the following code before the closing bracket of the class:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.reminders.count } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell:UITableViewCell! = tableView.dequeueReusableCellWithIdentifier("reminderCell") let reminder:EKReminder! = self.reminders![indexPath.row] cell.textLabel?.text = reminder.title let formatter:NSDateFormatter = NSDateFormatter() formatter.dateFormat = "yyyy-MM-dd" if let dueDate = reminder.dueDateComponents?.date{ cell.detailTextLabel?.text = formatter.stringFromDate(dueDate) }else{ cell.detailTextLabel?.text = "N/A" } return cell }
The cellForRowAtIndexPath protocol method will iterate through the reminders array and display each reminder’s title along with the formatted due date inside the cell.
Also, it’s important to make the class conform to the table view data source protocol, as well as the table view delegate protocol (you will implement its methods later). To do this, change the class declaration to the following:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
Before you go, set the delegate and datasource of the table view to the view controller, copy the following statements inside the viewDidLoad method:
self.tableView.dataSource = self self.tableView.delegate = self
So far so good, run the app and make sure your reminders are loaded. If the table view is empty, make sure to create some reminders in the Reminders app in your device and run the app again.
Here is my reminders retrieved inside the app, I got three in the queue 🙂
Remove a reminder:
Removing a reminder is even simpler than fetching reminders. Basically, you will call the removeReminder method on the event store object to synchronise the delete action with the Calendar database.
Go ahead and place the following data source protocol method implementation inside the ViewController.swift file:
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { let reminder: EKReminder = reminders[indexPath.row] do{ try eventStore.removeReminder(reminder, commit: true) self.reminders.removeAtIndex(indexPath.row) tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade) }catch{ print("An error occurred while removing the reminder from the Calendar database: \(error)") } }
The data source method above will run once you edit one of the table view rows. It will first get the EKReminder object in the selected row, then try to remove it from the event store (and hence from the Calendar database). The remove operation may throw errors, that’s why you run the code inside a do-catch block. If the reminder is removed successfully and the data are synchronized with the Calendar database, you then remove the EKReminder object from the table view and its data source array.
With the method above, you can swipe left on a cell to reveal the Delete button. Let’s also toggle the edit mode programmatically with a button click. Locate the editTable action method and implement the following code inside:
tableView.editing = !tableView.editing if tableView.editing{ tableView.setEditing(true, animated: true) }else{ tableView.setEditing(false, animated: true) }
The action method is attached to the edit bar button item on the left of the navigation bar.
Run the app and switch the table view to the edit mode, either by swiping left on a cell or by selecting the edit button on the navigation bar. Delete a reminder and switch back to the Reminders app in your device to make sure the removed reminder is also synchronized with the Calendar database 🙂
Create a new reminder:
Let’s extend our app to add a new reminder to the event store. If you run the app and try to click on the “+” button at the right of the navigation bar, another screen will show up, I already set up the segue in storyboard.
What you will do now is to pass the event store object to the next controller so that it will be used to create the new reminder.
Place the following code before the closing bracket of the ViewController.swift file:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { let newReminderVC = segue.destinationViewController as! NewReminder newReminderVC.eventStore = eventStore }
The method above will run right after a segue is being performed and right before the next controller is shown. The code you used inside will cast the destination controller to the NewReminder class, and assign the event store to its relevant EKEventStore property.
Select the NewReminder.swift file from the Project navigator view to open it in the editor. Now, add the following property declaration right after the class name:
var eventStore: EKEventStore!
Also, don’t forget to import the EventKit framework at the top of the file:
import EventKit
You are now in the new reminder screen, the event store is passed and can be used from within the NewReminder class.
In this screen, a UIDatePicker should be loaded once you click on the text field, so let’s set it up. Make the following changes to the NewReminder.swift file:
// Copy this declaration right after the class name var datePicker: UIDatePicker! // Place the following code inside the viewDidLoad method datePicker = UIDatePicker() datePicker.addTarget(self, action: "addDate", forControlEvents: UIControlEvents.ValueChanged) datePicker.datePickerMode = UIDatePickerMode.DateAndTime dateTextField.inputView = datePicker reminderTextView.becomeFirstResponder()
This code will create a UIDatePicker instance and set it to show up on the text field click, it will also call the addDate function each time the date is changed in the picker. Finally, you just set the reminderTextView as the first responder so you focus directly on typing in your new reminder details.
Let’s quickly add the triggered addDate function, place the following implementation inside the class:
func addDate(){ // Assign the date picker date to the text field self.dateTextField.text = self.datePicker.date.description }
Now, locate the action method called dismiss and place the following statement inside:
self.dismissViewControllerAnimated(true, completion: nil)
This will be attached to the Cancel button on the left of the navigation bar to dismiss the current screen and move back to the root controller.
Next, locate the method called saveNewReminder and implement the following code inside:
// 1 let reminder = EKReminder(eventStore: self.eventStore) reminder.title = reminderTextView.text let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate let dueDateComponents = appDelegate.dateComponentFromNSDate(self.datePicker.date) reminder.dueDateComponents = dueDateComponents reminder.calendar = self.eventStore.defaultCalendarForNewReminders() // 2 do { try self.eventStore.saveReminder(reminder, commit: true) dismissViewControllerAnimated(true, completion: nil) }catch{ print("Error creating and saving new reminder : \(error)") }
Let’s breakdown the code to better understand how it works:
// 1: This will create a new EKReminder instance with the event store property you previously passed to the view controller. Then it will set the title, dueDateComponents and calendar properties of the reminder with the relevant values. Note that you used a helper method called dateComponentFromNSDate to convert the UIDatePicker NSDate content to an NSDateComponents object and assign it to the dueDateComponents property.
// 2: Every Calendar database operation is done by the event store. Here, you just used the saveReminder API with the EKReminder object in argument. The API will try to save the reminder and if it succeeds, the screen will be dismissed to reveal the root view with all the current and new reminders in the table view.
Before you run the project, let’s quickly implement the helper method in the app delegate. Select AppDelegate.swift file from the Project navigator view, and place the following code inside:
func dateComponentFromNSDate(date: NSDate)-> NSDateComponents{ let calendarUnit: NSCalendarUnit = [.Hour, .Day, .Month, .Year] let dateComponents = NSCalendar.currentCalendar().components(calendarUnit, fromDate: date) return dateComponents }
Good, run the app and try to add a new reminder. After saving it, make sure it shows up in the table view as well as in the Reminders app in your device 🙂
Edit a reminder:
Editing a reminder is as easy as creating a new one! Let’s do this and I will let you judge by yourself 😉
Switch back to the ViewController.swift file and add the following property declaration to keep reference of the reminder you want to edit:
var selectedReminder: EKReminder!
Next, implement the following table view delegate method to track the click on the accessory button of the cell and launch the transition to the details screen of the reminder:
func tableView(tableView: UITableView, accessoryButtonTappedForRowWithIndexPath indexPath: NSIndexPath) { self.selectedReminder = self.reminders[indexPath.row] self.performSegueWithIdentifier("ShowReminderDetails", sender: self) }
The ShowReminderDetails is the segue identifier I set in storyboard to move from the root to the details screen.
Nothing fancy here except that there is now two segues that need to be managed. To distinguish between the two segues, locate the prepareForSegue method and update its implementation to the following:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "ShowReminderDetails"{ let reminderDetailsVC = segue.destinationViewController as! ReminderDetails reminderDetailsVC.reminder = self.selectedReminder reminderDetailsVC.eventStore = eventStore }else{ let newReminderVC = segue.destinationViewController as! NewReminder newReminderVC.eventStore = eventStore } }
This way, the method will know which segue is initiated based on the identifier set from the view controller’€™s storyboard file.
You are almost done, select the ReminderDetails.swift file from the Project navigator view, and make the following changes, this is almost the same work done for the NewReminder controller (I commented each code placement):
// At the top of the file import EventKit // Just below the class name var datePicker: UIDatePicker! var reminder: EKReminder! var eventStore: EKEventStore! // Inside the saveReminder action method self.reminder.title = reminderTextView.text let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate let dueDateComponents = appDelegate.dateComponentFromNSDate(self.datePicker.date) reminder.dueDateComponents = dueDateComponents reminder.calendar = self.eventStore.defaultCalendarForNewReminders() do { try self.eventStore.saveReminder(reminder, commit: true) self.navigationController?.popToRootViewControllerAnimated(true) }catch{ print("Error creating and saving new reminder : \(error)") } // Inside the viewDidLoad method self.reminderTextView.text = self.reminder.title datePicker = UIDatePicker() datePicker.addTarget(self, action: "addDate", forControlEvents: UIControlEvents.ValueChanged) datePicker.datePickerMode = UIDatePickerMode.DateAndTime dateTextField.inputView = datePicker reminderTextView.becomeFirstResponder()
And finally, implement the addDate method:
func addDate(){ self.dateTextField.text = self.datePicker.date.description }
That’s it!
Run the app on your device. Add, remove and edit your reminders, but most of all, enjoy your work 😉
As usual, you can download the final project here.
Feel free to leave a comment below, I always enjoy to hear from you!
If you got any question about this tutorial, feel free to ask me in the official forum thread here. Don’t hesitate to open a new forum thread if you got any other questions to ask, I am here to help.
Thanks! Save me!
You welcome!
return self.reminders.count app crashes at this point ! HELPP ! EXC_1386
Hi, can you provide me with more code from your project so I can see what’s going wrong?
Thanks for tutorial, how can i filter reminder between two days. Am using the code
let pastPredicate = eventStore.predicateForIncompleteRemindersWithDueDateStarting(today, ending:tomorrow, calendars: []) am getting null. How can i filter it ?
on saved date and time i can not get alert…i have perfectly store but not get reminder alert…please solve…
Hi, will you please update your tutorials for Swift 4? Thanks
Hi David, thanks for your comment. All tutorials will be updated to Xcode 9/Swift 4 so soon. I will keep you guys posted via email, please subscribe to get notified 😉