Reading and Writing Calendar Events

You can fetch, create, edit, and delete events from a user’s Calendar database using the EKEventStore class. You can fetch a custom set of events that match a predicate you provide, or you can fetch an individual event by its unique identifier. After you fetch an event, you can access its associated calendar information with the properties of the EKEvent class. Likewise, you can modify its calendar information by setting the properties of the EKEvent class.

Connecting to the Event Store

On iOS 5 and later, initialize an EKEventStore object with the default initializer:

EKEventStore *store = [[EKEventStore alloc] init];

On iOS 6 and later, you must request access to use the user’s Calendar database with the requestAccessToEntityType:completion: method after the event store is initialized. Requesting access to an entity type asynchronously prompts the user to allow or deny your app from using their calendar information. You should handle cases for when the user grants and denies access to your app:

[store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
    // handle access here
}];

On OS X, initialize an EKEventStore object with the designated initializer initWithAccessToEntityTypes::

EKEventStore *store = [[EKEventStore alloc] initWithAccessToEntityTypes:EKEntityMaskEvent];

You do not need to request access to use the user’s Calendar database on OS X, because access is automatically granted.

An EKEventStore object requires a relatively large amount of time to initialize and release. Consequently, you should not initialize and release a separate event store for each event-related task. Instead, initialize a single event store when your app loads, and use it repeatedly to ensure that your connection is long-lived.

An event store instance must not be released before other Event Kit objects; otherwise, undefined behavior may occur.

Retrieving Events

There are two ways to retrieve events. Fetching via predicates, or search query, will return zero or more events that match a given query. Fetching via unique identifiers will return a single event that corresponds to the given identifier.

Using Predicates

It’s common to fetch events that fall within a date range. The EKEventStore method eventsMatchingPredicate: fetches all events that fall within the date range specified in the predicate you provide. Listing 1-1 demonstrates how to fetch all events that occur between one day before and one year after the current date.

Listing 1-1  Fetching events with a predicate

// Get the appropriate calendar
NSCalendar *calendar = [NSCalendar currentCalendar];
 
// Create the start date components
NSDateComponents *oneDayAgoComponents = [[NSDateComponents alloc] init];
oneDayAgoComponents.day = -1;
NSDate *oneDayAgo = [calendar dateByAddingComponents:oneDayAgoComponents
                                              toDate:[NSDate date]
                                             options:0];
 
// 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 *predicate = [store predicateForEventsWithStartDate:oneDayAgo
                                                        endDate:oneYearFromNow
                                                      calendars:nil];
 
// Fetch all events that match the predicate
NSArray *events = [store eventsMatchingPredicate:predicate];

You can specify a subset of calendars to search by passing an array of EKCalendar objects as the calendars parameter of the predicateForEventsWithStartDate:endDate:calendars: method. You can get the user’s calendars from the event store’s calendarsForEntityType: method. Passing nil tells the method to fetch from all of the user’s calendars.

Because the eventsMatchingPredicate: method is synchronous, you may not want to run it on your app’s main thread. For asynchronous behavior, run the method on another thread with the dispatch_async function or with an NSOperation object.

Using Unique Identifiers

If you know the event’s unique identifier because you fetched it previously with a predicate, you can use the EKEventStore method eventWithIdentifier: to fetch the event. If it is a recurring event, this method will return the first occurrence of the event. You can get an event’s unique identifier with the eventIdentifier property.

Creating and Editing Events

Create a new event with the eventWithEventStore: method of the EKEvent class.

You can edit the details of a new event or an event you previously fetched from the Calendar database by setting the event’s corresponding properties. Some of the details you can edit include:

Saving and Removing Events

Changes you make to an event are not permanent until you save them. Save your changes to the Calendar database with the EKEventStore method saveEvent:span:commit:error:. If you want to remove an event from the Calendar database, use the EKEventStore method removeEvent:span:commit:error:. Whether you are saving or removing an event, implementing the respective method automatically syncs your changes with the calendar the event belongs to (CalDAV, Exchange, and so on).

If you are saving a recurring event, your changes can apply to all future occurrences of the event by specifying EKSpanFutureEvents for the span parameter of the saveEvent:span:commit:error: method. Likewise, you can remove all future occurrences of an event by specifying EKSpanFutureEvents for the span parameter of the removeEvent:span:commit:error: method.

Performing Batch Operations on Events

You can perform an operation on all events that match a provided predicate with the EKEventStore method enumerateEventsMatchingPredicate:usingBlock:. You must create the predicate for this method with the EKEventStore method predicateForEventsWithStartDate:endDate:calendars:. The operation you provide is a block of type EKEventSearchCallback.

typedef void (^EKEventSearchCallback)(EKEvent *event, BOOL *stop);

The block is passed two parameters:

event

The event that is currently being operated on.

stop

A Boolean value determining whether enumerateEventsMatchingPredicate:usingBlock: should stop processing events when this block returns. If YES, any event that matches the predicate but has not yet been processed will remain unprocessed.

Because the enumerateEventsMatchingPredicate:usingBlock: method is synchronous, you may not want to run it on your app’s main thread. For asynchronous behavior, run the method on another thread with the dispatch_async function or with an NSOperation object.