Using iOS 10 Event Kit to Create Date and Location Based Reminders

PreviousTable of ContentsNext
Receiving Data from an iOS 10 Action ExtensionAccessing the iOS 10 Camera and Photo Library


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


iOS includes the Reminders application, the purpose of which is to allow users to specify events about which they wish to be reminded. Reminders can be specified for a specific date and time, or even to be triggered when the user either arrives at or leaves a specified location. You might, for example, use the Reminders app to remind you to buy milk on your way home when your iPhone or iPad detects that you are leaving your office. Using the Event Kit Framework, it is possible to create and manage these reminders from within your own applications.

This chapter will cover some of the basics of calendars and reminders before working step-by-step through the creation of an example application that demonstrates the creation of both date and location based reminders.

An Overview of the Event Kit Framework

The Event Kit Framework consists of a range of classes designed specifically to provide access to the calendar database and to facilitate the management of events, reminders and alarms. In terms of integrating reminders into an iOS application, these classes are EKCalendar, EKEventStore, EKReminder and EKAlarm.

The EKEventStore class provides an interface between applications and the underlying calendar database. The calendar database can, in turn, contain multiple calendars (for example the user may have a work calendar and a personal calendar configured). Each calendar in a database is represented in code in the form of an EKCalendar object.

Within each calendar there are events and reminders, each of which is managed in code using the EKEvent and EKReminder classes respectively.

Finally, the EKAlarm class is used to configure alarms to alert the user at a specified point in the future.

The EKEventStore Class

In order to work with reminders in an iOS application, an instance of the EKEventStore class must be created. It is important to note that there is system overhead in requesting access to the calendar database so the call to initialize an EKEventStore object should ideally only be performed once within an application. In some situations, the system will prompt the user to allow the application access to the calendar. As such, the EKEventStore object should only be initialized immediately prior to the point in the code where calendar access is required. A reference to this event store object should then be retained and used for future calendar interaction throughout the lifespan of the application.

An EKEventStore object must request access to the calendar at the point that it is initialized. This request must specify whether access is required for calendar events (EKEntityTypeEvent) or reminders (EKEntityTypeReminder). Whether or not access is granted will depend on the current privacy settings for the application in the Privacy section of the device Settings app. Privacy settings are available for both Calendar and Reminders access. When an application seeks access for the first time, the user will be prompted by the system to allow the application to access either the calendar events or reminders. If the user declines access, the application will need to handle this gracefully. This is achieved via the completion handler of the requestAccess(to:) method of the EKEventStore class. The following code excerpt, for example, seeks access to reminders in the calendar database and reports an error in the console log in the event that access was declined:

var eventStore = EKEventStore()

eventStore.requestAccess(to: EKEntityType.reminder, completion:
               {(granted, error) in
            if !granted {
                print("Access to store not granted")
            }
    }) 

Once access has been accepted or denied by the user, the privacy setting for that application can be viewed and changed by selecting the application in Settings and choosing the Privacy option. Figure 90-1, for example, shows that the access for an application named ReminderApp to Reminders on the system is currently enabled.


Ios 8 app privacy settings.png

Figure 90-1


In addition, it is worth noting that the message used by the system to request access to the calendar database can be configured by adding an entry to the Info.plist file for the project. By default a message similar to that illustrated in Figure 90-2 will be displayed.


Ios 7 event kit reminder access.png

Figure 90-2


By editing the Info.plist file, for example, and adding a “Privacy – Reminders Usage Description” key and value, the message may be augmented to provide the user with additional information as to why access is required. Once defined, the custom message will appear when access is requested from the user.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


Accessing Calendars in the Database

As previously stated, the calendar database on an iOS device can contain multiple calendars, both for the events calendar and for reminders. An array of available calendars can be obtained via a call to the calendars(for:) method of the event store object, specifying as an argument whether event or reminder calendars are required. The following code outputs a list, by title, of the reminder calendars configured on the device. Note that each calendar entry in the array is represented by an EKCalendar object:

let calendars = 
	eventStore.calendars(for: EKEntityType.Reminder)

for calendar in calendars as [EKCalendar] {
    print("Calendar = \(calendar.title)")
}

The Event Kit Framework has the concept of a default calendar for the addition of new reminders and events. This default calendar may be configured by the user within the Settings app on the device (Settings -> Calendars -> Default Calendar). These are accessible in code via the defaultCalendarForNewReminders and defaultCalendarForNewEvents methods of the event store object. While some applications may need to let the user choose which calendar to use, these defaults can be useful when selection is not necessary.

Creating Reminders

New reminders are added to the calendar by creating new instances of the EKReminder class, configuring the object according to the requirements of the reminder and then adding it to the event store.

The following example code creates a reminder and adds it to the default calendar for new reminder entries:

let reminder = EKReminder(eventStore: self.eventStore)

reminder.title = "Go to the store and buy milk"
reminder.calendar = eventStore.defaultCalendarForNewReminders()

do {
    try eventStore.save(reminder,
            commit: true)
} catch let error {
        print("Reminder failed with error \(error.localizedDescription)")
}

The above code creates a new EKReminder object and, in so doing, associates it with the event store before setting the title of the reminder. Next, the reminder is configured so that it will be added to the user’s default reminder calendar before being saved to the event store.

Reminders can be general as in the above example or, as will be demonstrated in the following tutorial, configured to be triggered at a specific date and time, or when the user arrives at or departs from a physical geographical location (a concept known as geofencing).

Creating Alarms

The EKAlarm class can be used to add an alarm to the reminder. EKAlarm instances can be initialized either using a specific date and time (using an NSDate object) or using a relative time interval (via an NSTimeInterval value), for example:

let absoluteAlarm = EKAlarm(absoluteDate: date)
let relativeAlarm = EKAlarm(relativeOffset: TimeInterval) 

Once an EKAlarm object has been created and configured it must be added to the EKReminder object with which the alarm is to be associated. When the specified time arrives, the user will be notified of the reminder with sound, vibration and a notification panel.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Creating the Example Project

Begin by launching Xcode and selecting the options to create a new iOS application based on the Tabbed Application template. Enter ReminderApp as the product name, set the device menu to Universal and choose Swift as the programming language.

In the remainder of this chapter, an application will be constructed to allow the user to add reminders based on either date/time or location factors.

Setting up the Reminders Usage Description Key

An app may only access the reminders database when permission to do has been granted by the user. This involves the addition of the NSRemindersUsageDescription key to the project’s Info.plist file together with a string value explaining why the app needs this access. Within the project navigator panel, load the Info.plist file into the editor. The key-value pair needs to be added to the Information Property List dictionary. Select this entry in the list and click on the + button to add a new entry to the dictionary. Within the new entry, enter NSRemindersUsageDescription into the key column and, once the key has been added, double-click in the corresponding value column and enter the following text:

This access is required to set up reminders.

Designing the User Interface for the Date/Time Based Reminder Screen

Upon reviewing the Main.storyboard file, it is clear that Xcode has created a template-based tabbed application consisting of a Tab Bar Controller and two Views, each of which has its own view controller. For the purposes of this example, the first view will be used to implement a screen whereby the user can create a new date and time based reminder. Within the Storyboard canvas, therefore, locate the First View screen and remove the current labels that were added by Xcode. With a clean view, add a Text Field, Date Picker and Button object to the view canvas. Once added, position and configure the user interface layout so that it resembles that of Figure 90 3:


Xcode 8 ios 10 reminder app first ui.png

Figure 90-3


Shift-click on all three views so that all of the objects are selected, then use the Auto Layout Align menu to position the views in the horizontal center of the container. Select the Text Field view and, using the Auto Layout Add New Constraints menu, configure a Spacing to nearest neighbor constraint on the top edge of the view using the current distance value and with the Constrain to margins option switched off. Within the Add New Constraints panel, also enable the Width constraint using the current value before clicking on the Add 2 Constraints button.

Next, select the Button view and add a constraint on the top edge to the nearest neighbor, once again with the Constrain to margins option switched off.

Repeat the above step with the Date Picker view selected, this time also enabling the Height and Width constraints.

Select the Text Field object and display the Assistant Editor. Ctrl-click on the Text Field object in the view and drag the resulting line to the area immediately beneath the class declaration line in the Assistant Editor panel. Upon releasing the line, the configuration panel will appear. Configure the connection as an Outlet named reminderText and click on the Connect button. Repeat this step to add an outlet connection to the Date Picker object named myDatePicker.

Finally, Ctrl-click on the Button object, drag the line to the Assistant Editor and release it beneath the end of the viewDidLoad method. In the resulting connection panel, change the connection type to Action and name the action setReminder.

Implementing the Reminder Code

With the user interface view designed, the next step is to implement the code in the view controller for the first view to access the event store and set up the reminder. We will need a place to store the event store object once it has been requested and, as previously discussed, it is recommended that access to the event store be requested only once. As such we will need a location to store the reference once it has been obtained, so select the AppDelegate.swift file, import the EventKit Framework and declare a variable in which to save the event store reference:

import UIKit
import EventKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var eventStore: EKEventStore?
.
.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Next, edit the FirstViewController.swift file, import the EventKit Framework and add a variable in which to store a reference to the application delegate:

import UIKit
import EventKit

class FirstViewController: UIViewController {

    @IBOutlet weak var reminderText: UITextField!
    @IBOutlet weak var myDatePicker: UIDatePicker!
    let appDelegate = UIApplication.shared.delegate
                as! AppDelegate
.
.

Locate the setReminder template stub and add the following code:

@IBAction func setReminder(_ sender: AnyObject) {

   if appDelegate.eventStore == nil {
        appDelegate.eventStore = EKEventStore()

    appDelegate.eventStore?.requestAccess(
        to: EKEntityType.reminder, completion: {(granted, error) in
            if !granted {
                print("Access to store not granted")
                print(error?.localizedDescription)
            } else {
                    print("Access granted")
            }
        })
    }

    if (appDelegate.eventStore != nil) {
        self.createReminder()
    }
}

The code added to the method verifies that access to the event store has not already been obtained and, in the event that it has not, requests access to the reminder calendars. If access is denied a message is reported to the console. In the event that access is granted, a second method named createReminder is called. With FirstViewController.swift still in the editing panel, implement this method:

func createReminder() {

    let reminder = EKReminder(eventStore: appDelegate.eventStore!)

    reminder.title = reminderText.text!
    reminder.calendar =
    appDelegate.eventStore!.defaultCalendarForNewReminders()
    let date = myDatePicker.date
    let alarm = EKAlarm(absoluteDate: date)

    reminder.addAlarm(alarm)

    do {
        try appDelegate.eventStore?.save(reminder,
            commit: true)
    } catch let error {
            print("Reminder failed with error \(error.localizedDescription)")
    }
}

The createReminder method creates a new EKReminder object associated with the event store and sets the title property to the content of the Text Field object in the user interface. The code elects the default calendar as the target for the reminder and then creates an EKAlarm object primed with the date value selected by the user in the Date Picker object. The alarm is then added to the reminder which, in turn, is saved in the event store. Errors are output to the console for debugging purposes.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Hiding the Keyboard

Before moving on to the next part of the tutorial, some code will now need to be added to the application so that the keyboard is withdrawn when the user touches the view background.

Within the FirstViewController.swift file, override the code in the touchesBegan method:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    reminderText.endEditing(true)
}

With the time and date based reminder phase of the application completed, the next step is to implement the location based reminder functionality.

Designing the Location-based Reminder Screen

The tab controller created on our behalf by Xcode contains a second view that will be used for the location based reminder creation. The goal of this view will be to allow the user to specify a reminder and alarm that will be triggered when the user moves away from the geographical location at which the reminder was created.

Begin by selecting the Main.storyboard file and locating the second view controller scene. Remove the template labels added by Xcode and design a new user interface using a Button and a Text Field as illustrated in Figure 90-4.

Shift-click on both views so that all of the objects are selected, then use the Auto Layout Align menu to position the views in the horizontal center of the container.

Select the Text Field view and, using the Auto Layout Add New Constraints menu, configure a Spacing to nearest neighbor constraint on the top edge of the view using the current distance value and with the Constrain to margins option switched off. Within the Add New Constraints panel, also enable the Width constraint using the current value before clicking on the Add 2 Constraints button.

Next, select the Button view and add a constraint on the top edge to the nearest neighbor, once again with the Constrain to margins option switched off.

Using the Assistant Editor (taking care to ensure that the editor displays the code for the SecondViewController.swift file and not the file for the first view controller), establish an outlet connection for the Text Field named locationText. Next, establish an Action connection from the button to a method named setLocationReminder.


Xcode 8 ios 10 reminder app second ui.png

Figure 90-4


Finally, add an application delegate property to the SecondViewController.swift file and import the Event Kit header:

import UIKit
import EventKit

class SecondViewController: UIViewController {

    @IBOutlet weak var locationText: UITextField!
    let appDelegate = UIApplication.shared.delegate 
		as! AppDelegate
.
.
.

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

Creating a Location-based Reminder

The code for accessing the event store is the same as that for the date/time example. Since the application will need to get the user’s current location, the CoreLocation framework will need to be imported. The code will require the use of CLLocationManager so a property for the manager instance will also be needed, as will a declaration that the view controller now implements the CLLocationManagerDelegate protocol. The viewDidLoad method also needs to be modified to request access to location data on the device and to configure the location manager instance:

import UIKit
import EventKit
import CoreLocation

class SecondViewController: UIViewController, CLLocationManagerDelegate {

    @IBOutlet weak var locationText: UITextField!
    let appDelegate = UIApplication.shared.delegate
                as! AppDelegate
    var locationManager: CLLocationManager = CLLocationManager()


    override func viewDidLoad() {
        super.viewDidLoad()

        locationManager.requestWhenInUseAuthorization()
        locationManager.delegate = self
        locationManager.distanceFilter = kCLDistanceFilterNone
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
    }
.
.
.

All that remains is to implement the code to access the event store and create the alert and reminder. Still within the SecondViewController.swift file, modify the setLocationReminder method as follows:

@IBAction func setLocationReminder(_ sender: AnyObject) {

    if appDelegate.eventStore == nil {
        appDelegate.eventStore = EKEventStore()

        appDelegate.eventStore?.requestAccess(
            to: EKEntityType.reminder, completion:
           {(granted, error) in
               if !granted {
                   print("Access to store not granted")
               } else {
                   print("Access granted")
               }
        })
    }
}

As with the previous example, access to the event store is requested. This time, however, a CLLocationManager instance is created and started up. This will result in a call to the didUpdateLocations and didFailWithError location delegate methods where the code to obtain the current location and to create the alarm and reminder will need to be implemented:

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    let reminder = EKReminder(eventStore: appDelegate.eventStore!)
    reminder.title = locationText.text!
    reminder.calendar =
         appDelegate.eventStore!.defaultCalendarForNewReminders()

    let location = EKStructuredLocation(title: "Current Location")
    location.geoLocation = locations.last

    let alarm = EKAlarm()

    alarm.structuredLocation = location
    alarm.proximity = EKAlarmProximity.leave

    reminder.addAlarm(alarm)

    do {
        try appDelegate.eventStore?.save(reminder,
        commit: true)
    } catch let error {
        print("Reminder failed with error \(error.localizedDescription)")
    }
}

func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
    print("Failed to get location: \(error.localizedDescription)")
}

Since this code introduces some new concepts a more detailed breakdown is probably warranted. To begin, a new EKReminder instance is created and initialized with the text entered by the user into the Text Field.

let reminder = EKReminder(eventStore: appDelegate.eventStore!)
reminder.title = locationText.text!

The default calendar is selected to store the reminder and then an EKStructuredLocation instance created with a location title of “Current Location”. This is the title by which the location will be listed in the Reminders app. The most recent location from the location update is extracted from the end of the locations array (see chapter Getting Location Information using the iOS 10 Core Location Framework for more details on location awareness) and the coordinates assigned to the EKStructuredLocation object.

reminder.calendar = 
         appDelegate.eventStore!.defaultCalendarForNewReminders()

let location = EKStructuredLocation(title: "Current Location")
location.geoLocation = locations.last

Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book

The location is then added to a newly created alarm instance which is subsequently configured to be triggered when the user moves away from the location proximity:

let alarm = EKAlarm()
alarm.structuredLocation = location
alarm.proximity = EKAlarmProximity.Leave

reminder.addAlarm(alarm)

Finally, the fully configured reminder is saved to the event store.

do {
    try appDelegate.eventStore?.save(reminder,
        commit: true)
} catch let error {
    print("Reminder failed with error \(error.localizedDescription)")
}

Setting up the Usage Description Key

The above code changes included a method call to request permission from the user to track location information when the application is running in the foreground. This method call must be accompanied by a usage description string which needs to be added to the project’s Info.plist file assigned to the NSLocationWhenInUseUsageDescription key. Within the project navigator panel, load the Info.plist file into the editor. The key-value pair needs to be added to the Information Property List dictionary. Select this entry in the list and click on the + button to add a new entry to the dictionary. Within the new entry, enter NSLocationWhenInUseUsageDescription into the key column and, once the key has been added, double-click in the corresponding value column and enter the following text:

This information is required to access your current location

Testing the Application

Since the application will rely on a default calendar having been designated for reminders, the first step is to make sure this has been configured. Launch the Settings application, scroll down to and then select Reminders, and make sure that a calendar has been assigned to the Default List.

Compile and run the application on a physical device (reminders do not currently work on the iOS Simulator). Select a time a few minutes into the future, and enter some text onto the first screen before touching the Set Reminder button. Put the app into the background and launch the built-in Reminder app where the new reminder should be listed in the default reminder list. When the designated time arrives the alarm should trigger, displaying the text entered by the user.

Using the tab bar, switch to the second screen, enter a message and touch the “When I Leave Here” button. Once again, switch to the built in Reminders app and open the default calendar where the new reminder should now be listed. Select the reminder so that the blue information button appears. Tap on this button to review the reminder details as shown in Figure 90-5:


Ios 8 location reminder details.png

Figure 90-5


When you next leave your current location the alarm should trigger.

Summary

The Event Kit Framework provides a platform for building reminders into iOS applications. Reminders can be triggered based on either a date and time or change of geographical location. This chapter has provided an overview of Event Kit based reminders before working through the creation of an example application.


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book



PreviousTable of ContentsNext
Receiving Data from an iOS 10 Action ExtensionAccessing the iOS 10 Camera and Photo Library