Apple’s Event Kit framework (introduced in the iOS 4.0 SDK) lets developers interact with a device’s calendar database. You can not only read out existing events but also create new calendar events and even entirely new calendars from your app. In this article, let’s have a look at how creating and deleting calendars works.
Creating a New Calendar
Before we start, don’t forget to add the EventKit.framework
to your project and #import <EventKit/EventKit.h>
in your precompiled header file.
Creating a Local Calendar
Creating a new calendar and saving it to the calendar database is pretty straightforward. All you have to do is ask the event store to create a calendar, set its title and its source. The source is an object of the EKSource
class that describes the underlying service the calendar belongs to. Currently, the following source types are defined: EKSourceTypeLocal
, EKSourceTypeExchange
, EKSourceTypeCalDAV
, EKSourceTypeMobileMe
, EKSourceTypeSubscribed
, EKSourceTypeBirthdays
.
Since it is not possible to create new EKSource
instances, we have to iterate over the existing sources in the event store and assign the one we want (here: EKSourceTypeLocal
) to the new calendar. The code looks like this:
EKEventStore *eventStore = [[EKEventStore alloc] init];
EKCalendar *calendar = [EKCalendar calendarWithEventStore:eventStore];
calendar.title = CALENDAR_TITLE;
// Iterate over all sources in the event store and look for the local source
EKSource *theSource = nil;
for (EKSource *source in eventStore.sources) {
if (source.sourceType == EKSourceTypeLocal) {
theSource = source;
break;
}
}
if (theSource) {
calendar.source = theSource;
} else {
NSLog(@"Error: Local source not available");
return;
}
Now we need to save the calendar to the event store. If the save is successful, we store the calendar’s calendarIdentifier
(a UUID generated by the event store) to access it later. You should save this identifier to the user defaults in order to get the calendar back in future runs of the app.
NSError *error = nil;
BOOL result = [eventStore saveCalendar:calendar commit:YES error:&error];
if (result) {
NSLog(@"Saved calendar to event store.")
self.calendarIdentifier = calendar.calendarIdentifier;
} else {
NSLog(@"Error saving calendar: %@.", error);
}
Creating a Calendar in iCloud
Interestingly, you can use the same technique to create a new calendar in the user’s iCloud account directly from the device. Just iterate over all sources the event store has available and try to identify the iCloud account. On my iPhone, the sourceType
of the iCloud source is EKSourceTypeCalDAV
(note that it’s not EKSourceTypeMobileMe
!) and the title
is @"iCloud"
. I am not 100% certain that these values reliably identify the iCloud source (perhaps the user can change its title somehow?) but it seems likely.
...
// Iterate over all sources in the event store and look for the local source
EKSource *theSource = nil;
for (EKSource *source in eventStore.sources) {
if (source.sourceType == EKSourceTypeCalDAV && [source.title isEqualToString:@"iCloud"]) {
theSource = source;
break;
}
}
...
A few seconds after creating an iCloud-based calendar on my iPhone with this code, it popped up in the calendar list in iCal on my Mac. In your production app, you should always be prepared for the case that the event store does not offer iCloud as an available source. If the user hasn’t set up an iCloud account, the source will be missing from the list. The same could be true if the user does have an iCloud account but does not use it to sync any other calendars. In such a case, your app should probably fall back to creating a local calendar.
I have not tested whether you can also create calendars on other services (e.g., MS Exchange) this way. I would be interested to know your results if you proceed to test this.
Update February 12, 2014 and March 05, 2014: Robert de Jong pointed out to me that the source title is indeed user-editable (Settings > iCloud > Account > Description), which makes this way of detecting the iCloud calendar store unreliable. Robert suggests this approach:
EKSource *theSource = [[eventStore defaultCalendarForNewEvents] source];
For most users, this should provide the iCloud source, if it’s set up, or the local source, if not. But it might as well return a totally different source if the user has configured another default. There seems to be no 100% reliable way to retrieve the iCloud source with public API.
Creating Events
To create an event in our newly created calendar, just ask the event store for the calendar based on the identifier we stored, and assign the calendar to the new event:
EKEventStore *eventStore = [[EKEventStore alloc] init];
EKEvent *event = [EKEvent eventWithEventStore:eventStore];
EKCalendar *calendar = [eventStore calendarWithIdentifier:self.calendarIdentifier];
event.calendar = calendar;
// Set the start date to the current date/time and the event duration to one hour
NSDate *startDate = [NSDate date];
event.startDate = startDate;
event.endDate = [startDate dateByAddingTimeInterval:3600];
And to save the event to the event database:
NSError *error = nil;
BOOL result = [eventStore saveEvent:event span:EKSpanThisEvent commit:YES error:&error];
if (result) {
NSLog(@"Saved event to event store.")
} else {
NSLog(@"Error saving event: %@.", saveError);
}
Deleting a Calendar
In my tests, I found that the calendar app on iOS does not let me delete a calendar that was created by my own test app. Since the app offers the option to delete such a calendar but simply does not do it, this looks like a bug on Apple’s side. When you sync the newly created calendar to the Mac (via iTunes or iCloud), you can delete it in iCal on Lion without problems.
To delete a calendar in code, do this:
EKEventStore *eventStore = [[EKEventStore alloc] init];
EKCalendar *calendar = [eventStore calendarWithIdentifier:self.calendarIdentifier];
if (calendar) {
NSError *error = nil;
BOOL result = [self.eventStore removeCalendar:calendar commit:YES error:&error];
if (result) {
NSLog(@"Deleted calendar from event store.");
} else {
NSLog(@"Deleting calendar failed: %@.", error);
}
}