Date Time Icon

Organize Your Enterprise – Calendar and Reminders in EventKit

An Overview of EventKit in iOS 6

Introduction

While calendars, events, and reminders aren’t the most exciting aspect of mobile devices they do play an important role. If you are an iOS developer, your app may benefit from incorporating the ability to interact with a user’s calendars or reminders.

To begin, let’s review some aspects of the calendar and reminder facilities in iOS . First, the concept of a calendar (EKCalendar). This is the container that will hold our events and reminders. A calendar has particular properties including some basic ones like title and color, but also is defined by attributes like a source, such as Exchange, MobileMe, Subscribed, etc. The backend storage for a calendar can be local or remote in the case of CalDAV or Exchange.

The next two concepts are events (EKEvent) and reminders (EKReminder). These two concrete classes extend from EKCalendarItem, which exposes properties and attributes shared by both. The EKCalendarItem‘s super class,EKObject, provides persistence support for the object including rollback, refresh, and reset functions.

Technical Overview

Getting Started

As you may have discerned, the EventKit API is a framework included with the iOS SDK and must be added to your project via the Build Phases tab. You also need to import EvenKit in the appropriate classes as shown below.

#import <EventKit/EventKit.h>

 The first step to working with calendars and reminders is to request access to the type you wish to interact with.  There are two entity types, one for calendar items (events) and one for reminders.  You distinguish between the two using either EKEntityTypeEvent or EKEntityTypeReminder respectively.

New in iOS 6 are privacy settings giving granular control to the user of what apps have access to particular system-wide facilities including calendars and reminders. As an app developer using the EventKit API, you must request access and be granted this access from the user to the stores you need.

Request And Being Granted Access

In the API there is a class method that allows you to “check” on the current permission status. With this call, you will receive back a status code explaining the current disposition of your app’s permission without actually requesting access from the user.

KAuthorizationStatus eventAuthStatus = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent];

The EKAuthorizationStatus type is an enum consisting of four possible values, two indicating denied permissions (EKAuthorizationStatusRestricted, EKAuthorizationStatusDenied), one indicating allowed permission (EKAuthorizationStatusAuthorized), and one indicating the decision has not been made yet (EKAuthorizationStatusNotDetermined). The difference between the “restricted” and “denied” value is that “denied” means the user actively made that decision while “restricted” means the option was not presented to the user and is not changeable by the user, perhaps set from a mobile profile or parental controls. The “not determined” value indicates the user has not been prompted yet to grant or reject the permission.

The following code snippet provides how to initialize a store and request of the user permission to access the store for a given type. This code is asynchronous and has a completion block. If this is the first time the code is executing, the user will be prompted to allow or reject access. If the code had run before, the result of the user’s decision will be used and reflected in the block’s granted boolean variable.

// For iOS 6.0 and later
EKEventStore *_eventStore [[EKEventStore alloc] init];
[_eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
    // handle access here
}];
 
EKEventStore *_reminderStore [[EKEventStore alloc] init];
[_eventStore requestAccessToEntityType:EKEntityTypeReminder completion:^(BOOL granted, NSError *error) {
    // handle access here
}];

More information about checking on and requesting permissions can by found in this blog post covering the new data isolation requirements included in iOS 6.0.

Opening Up A Calendar For Events And Reminder

After you have requested and been granted access to a store, you’ll have a handle to the calendar store. The persistence backend for the store will provide you access to both calendars and reminders, provided you have been granted permission to access both. As a note, this store object should be long-lived as there is a reasonable cost to the initialization, permission checks, and construction of the object.

The snippet below uses the the event store handler obtained when requesting permission to retrieve a handler to the default event / reminder store.

EKCalendar *defaultEventStore = [_eventStore defaultCalendarForNewEvents];
EKCalendar *defaultReminderStore = [_eventStore defaultCalendarForNewReminders];
 
NSLog(@"event store details: %@", defaultEventStore.description);
NSLog(@"reminder store details: %@", defaultReminderStore.description);

If you need to present the user with a list of event or reminder calendars for them to pick from or you wish to use a different calendar than the default, you can obtain an array of calendars for events and reminders asEKCalendar objects as seen in the snippet below, which gets a list of event calendars and log out the description of the object.

NSArray *eventCalendars = [_eventStoreEvent calendarsForEntityType:EKEntityTypeEvent];
 
for (EKCalendar *eventCalendar in eventCalendars) {
    NSLog(@"an event calendar ... %@", eventCalendar.description);
}

Creating A New Calendar

If you are feeling really bold and wish to create a new calendar, there are a few steps involved. The first step after being granted access, is to obtain a source for the calendar. The object that contains the source is EKSourceand the event store can be queried to understand what sources it supports. The following snippet demonstrates how to query the event store for its sources and select first local source. Once the first local source is found we save a handle to it by assigning it to the myLocalSource variable.

EKSource *myLocalSource = nil;
for (EKSource *calendarSource in _eventStore.sources) {
	if (calendarSource.sourceType == EKSourceTypeLocal) {
		myLocalSource = calendarSource;
		break;
	}
}

Next we need to create our calendar object using the source determined above, give it a title, and save it. As a general rule, you should always pass a referene to an NSError variable and check it for any errors that may have occured. After the calendar is created, saved, and commited successfully, your calendar will be assigned an identifier. You may be interested in getting that value and saving it off, but you can always obtain it later if needed. The commit parameter allows for the batching of operations if several “saves” will be occuring. To batch your savings, pass NO to the commit parameter to all but the final save message. When saving the final calendar you would pass YES. We will see this concept again when we create and edit events, reminders, and other EventKit objects.

// Create a new calendar of type Local... save and commit
EKCalendar *myCalendar = [EKCalendar calendarForEntityType:EKEntityTypeEvent eventStore:_eventStore];
myCalendar.title = @"My New Calendar";
myCalendar.source = myLocalSource;
 
NSError *error = nil;            
[_eventStore saveCalendar:myCalendar commit:YES error:&amp;error];
 
if (!error) {
    NSLog(@"created, saved, and commited my calendar with id %@", myCalendar.calendarIdentifier);
} else {
    NSLog(@"an error occured when creating the calendar");
    error = nil;
}

There are more options that can be applied including a color, as well as attributes that define what “entity types” can be stored in the calendar. While it is not often a calendar will hold both events and reminders, it can occur.

Working With Dates And Times

At this point in the post, I want to quickly discuss working with dates and times. While the concept of date and time is relatively simple, dealing with these in code is not. Our planet has the interesting concept of time zones in addition to the concept in the United States of daylight savings time. But, even with daylight savings time, not all states participate and there are even some states where only part of the state participates. For these reasons, the task to accurately show the user a date and time that is meaningful given their location is not an easy one, but luckily iOS does provide a straight forward path for success.

When dealing with calendar items (events, reminders, alarms), dates and times come into play. Care must be taken when working with date values and performing calculations on them. Instead of you, the developer, attempting to figure out the time zone of the user or resolve any other specific date and time rules, it is best to rely on the configured locale of the device. The best way to handle this is operate against the Gregorian calendar and use date components to construct values used in calculations. The following code demonstrates obtaining a handle on the current calendar for the user and constructing two specific NSDate objects that can be used with an event, reminder, or alarm.

// Get the appropriate calendar
NSCalendar *calendar = [NSCalendar currentCalendar];
 
NSDateComponents *oneDayComponents = [[NSDataComponents alloc] init];
oneDayComponents.day = 1;
NSData *tomorrow = [calendar dateByAddingComponents:oneDayComponents
											  toDate:[NSData date]
											options:nil];
 
NSDateComponents *oneYearFromNowComponents = [[NSDateComponents alloc] init];
oneYearFromNowComponents.year = 1;
NSDate *oneYearFromNow = [calendar dateByAddingComponents:oneYearFromNowComponents
	                                                  toDate:[NSDate date]
	                                                 options:0];

As a note, don’t be fooled by logging out the NSDate as the description may not be the true date that will be used. For example, your device may be in the America/New_York (EDT) timezone, yet an NSLog of your NSDateshows a GMT timezone. Use the NSDataFormatter to output the NSDate for display as shown bellow if we need to log out a date variable.

NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:@"yyyy-MM-dd HH:mm:ss ZZ"];
NSString *dateTimeWithTimeZone = [df stringFromDate:[NSDate date]];
NSLog(@"the date is %@",dateTimeWithTimeZone);

Event and Reminder Objects

In EventKit, the first class citizens EKEvent and EKReminder share a parent, the EKCalendarItem class. This parent class provides properties in common to both events and reminders as well as provides services that each needed. A few properties to point out are some basics like title, location, creation date, and notes. Additional properties include what calendar the item belows to, if the item has any alarms set on it, what contacts are associated with the object known as attendees, and if there are any recurrence rules.

Creating An Event

Now that we have opened up a default calendar or created a new calendar, we can add a new event to the calendar. The first step is to create an event object (EKEvent) that we will populate, assign to a calendar, then save. Saving events follows a similar pattern for commits, you can batch “save” operations by passing NO to all operations except the last one.

After the event has been saved and committed successfully, the event object will be populated with an identifier (eventIdentifier) that uniquely identifies this event in the event store. The code below demonstrates the creation of a simple event with a save, commit, error check, and then querying the event store for the specific event identifier.

// Create a new event... save and commit
NSError *error = nil;
EKEvent *myEvent = [EKEvent eventWithEventStore:_eventStore];
myEvent.allDay = NO;
myEvent.startDate = [NSDate date];
myEvent.endDate = [NSDate date];
myEvent.title = @"Finish Blog Post";
myEvent.calendar = myCalendar;
[_eventStore saveEvent:myEvent span:EKSpanThisEvent commit:YES error:&error];
 
if (!error) {
    NSLog(@"the event saved and committed correctly with identifier %@", myEvent.eventIdentifier);
} else {
    NSLog(@"there was an error saving and committing the event");
    error = nil;
}
 
EKEvent *savedEvent = [_eventStore eventWithIdentifier:myEvent.eventIdentifier];
NSLog(@"saved event description: %@",savedEvent);

Retrieving Events Using Search Predicates

As with other Apple APIs, you can refine your event store queries using an instance of NSPredicate. For events and reminders, you can query the event store using a predicate to find items that match certain criteria.

The example below demostrates settings up a predicate based on time in which our criteria will be the time frame starting now and ending one year from now. We will be quering the event store and focusing specifically on the default calendar.

// Get the appropriate calendar
NSCalendar *calendar = [NSCalendar currentCalendar];
 
// Create the end date components
NSDateComponents *oneYearFromNowComponents = [[NSDateComponents alloc] init];
oneYearFromNowComponents.year = 1;
NSDate *oneYearFromNow = [calendar dateByAddingComponents:oneYearFromNowComponents
	                                                   toDate:[NSDate date]
	                                                  options:0];
 
// Create the predicate from the event store's instance method
NSPredicate *eventSearchPredicate = [_eventStore predicateForEventsWithStartDate:[NSDate date]
	                                                      endDate:oneYearFromNow
	                                                    calendars:@[defaultEventCalendar]];
 
NSArray *eventsInTheDefaultCalendar = [_eventStore eventsMatchingPredicate:eventSearchPredicate];
for (EKEvent *event in eventsInTheDefaultCalendar) {
	NSLog(@"an event in the default calendar: %@", event);
}

Notice we are using the date components to construct our date bounds. Also, notice the use of the new literal syntax to create the array of calendars, which is new to iOS 6.0. You can set this value to nil, which will indicate to the event store to search all calendars.

Setting Alarms

While calendar events and reminders provide a mechanism to capture items we need to remember, another mechanism is available to alert us when we need to remember; the alarm.

For each calendar item, an alarm can be created. An alarm can be set for a specific date and time, known as an absolute alarm, a relative time based on the calendar event, known as a relative offset, or an alarm that is tiggered when you enter a specific location, known as a structured location alarm.

To create an alarm, create an instance of the EKAlarm class. There are two class methods that can used to create the instance; alarmWithAbsoluteDate and alarmWithRelativeOffset.

Once you have created the specific type of alarm you wish to set, you can add this alarm to an event or reminder by calling the addAlarm: method.

Multiple alarms can be added to an individual event or reminder, and they are stored in an NSSArray with the property name alarms.

Creating Recurrence Rules

Topics On Reminders

Next I want to discuss reminders. I provided a lot of examples of working with calendars and events including event creation and searching. Reminders work in a very similar way, but there are a few particular differences that I will point out in the next few sections.

Creating A Reminder

Creating a reminder is accomplished by creating an instance ofEKReminder. After establishing an event store for the reminder entity type, create the reminder object as follows: EKReminder *reminder = [EKReminder reminderWithEventStore:_reminderEventStore];

With the reminder created and associated to the event store, an important next step is to assign the reminder to a calendar. The purpose of this is to estalish which “list” the reminder should be associated to. The calendarproperty is inheritied from the parent class, EKCalendarItem.

At this point, you can set other properties desired, such as title, notes, and attendees. Located directly on the EKReminder class itself are the start date and due date properties set as NSDateComponents objects.

Completing a Reminder

To complete a reminder, set the completed property to YES on the reminder object. When you do this, the completionDate property is automatically set to today’s date. If you toggle the reminder’s completed boolean to NO, the completedDate property is set to nil.

Location-Based Reminders

Creating a date and time based reminder is relatively straight forward and commonly used. An interesting twist on reminders are the location-based reminders. With this technique, you can provide a location and a radius from the location, creating what is known as a geo-fence around the location. With this geo-fence, you can create a reminder that is triggered when you enter or exit the bounds of the fence.

The first step is to create a structured location object that will contain the location title, the geo-location to center our geo-fence around, and a radius. The geo-location paired with the radius defines the geo-fence. The geo-location is provided as a CLLocation object, which can be created in its simplest form giving the latitute and longitude degrees.

Once you have the EKStructuredLocation object, you create an instance of EKAlarm and assigned to its structuredLocation property your created EKStructuredLocation object. You must also specify the proximityproperty EKAlarmProximity in order for the location to take effect. By default, the location is ignored, but you can specify that the alarm should be triggered upon entering or leaving the geo-fence.

Now that you have an EKAlarm object, this can be assigned to an EKReminder object using the inheritited method addAlarm.

References