This Is My Blog

There are many like it, but this one is mine.

Accessing Reminders With EventKit (Part 1)

I’m old school when it comes to certain things. When the Titanium Powerbook G4 came out, I was excited because I could have six Terminal windows open at once. I still use Emacs. My biggest complaint about Xcode 4 is they got rid of the Emacs client integration.

On the other hand, sometimes newer things work for me as well. When Reminders came out in iOS 5, I started using it, but wasn’t diligent about it. Once it got iCloud and Mountain Lion integration, I started using it a lot more. I even use Siri to make a reminder. I make a reminder on my iPhone, and I have it on all my devices, so I actually keep track of things.

Recently, I started using the command line password manager, pass, to manage my passwords. I tried other password managers, and this is a case where the simpler command-line interface works for me. You type pass and get a list of all your accounts and passwords, which you catagorize. There are simple commands to add, extract and delete passwords. It’s all encrypted using GPG, so I don’t need to worry if I lose my laptop. All the data is stored in my home directory, so it’s part of my Time Machine backups.

pass
1
2
3
4
5
6
7
8
9
10
11
12
zx2c4@laptop ~ $ pass
Password Store
├── Business
│   ├── some-silly-business-site.com
│   └── another-business-site.net
├── Email
│   ├── donenfeld.com
│   └── zx2c4.com
└── France
    ├── bank
    ├── freebox
    └── Mobilephone

I wanted some similar for to access reminders. Now Apple’s been pretty good about making command-line equivalents for a lot of UI tools (think DiskUtility.app and diskutil), but this is one where they don’t have anything. A quick Google didn’t really find anything for me either.

So I have roll up my sleeves and write something myself.

Turns out that along with releasing Reminders in Mountain Lion, Apple has given us an SDK to access the data store behind Calendar and Reminders: EventKit. EventKit was first introduced in the OSX and iOS SDKs to give access to Calendar. Now with OSX Mountain Lion and iOS 6, Apple extended EventKit to grant access to Reminders. This extension to EventKit was possible because underneath it all, items in Reminders (“reminders”) are an extension of events in Calendar (“events”). In fact, both sets of data are stored in a single database, which Apple calls the Calendar database.

Why not use something like SQLite and let us access the calendar database directly? Odds are that your local calendar(s) are synchronized with calendars on server (i.e. iCloud, gCal, etc), typically using CalDAV. In order to maintain that synchronization, it’s easier to mediate all access to the local Calendar database to know when changes need to propagated to the server and known when changes have arrived from the server.

Note: I’m building an application to run in OSX. Most of what I cover is applicable for iOS, but there are some differences. For example, instantiating the event store is different between OSX and iOS. Read the Calendar and Reminders Programming Guide. Also, I’m building an application to access reminder, I won’t be covering event access.

Let’s review the EventKit framework with respect to reminders.

Event Store

At the root of EventKit is the class EKEventStore. This represents the Calendar database. EKEventStore is a fairly heavy-weight object, taking a (relatively) long time to instantiate and release. As a result, your application should only instantiate a single EKEventStore.

To instantiate your event store, you invoke the initializer, initWithAccessToEntityType:, and EKEntityMask for reminders.

1
EKEventStore *store = [[EKEventStore alloc] initWithAccessToEntityType:EKEntityTypeReminder];

With iOS, you instantiate your event store with a simple call to init. Then, you request access to reminders using requestAccessToEntityType:completion:. This invoking this method will cause iOS to ask the user if your application is allowed to access your Calendar database. You need to handle both cases in the completion block. This code is not in the OSX EventKit, since access to the Calendar database is granted automatically.

1
2
3
4
5
EKEventStore *store = [EKEventStore alloc] init];

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

Reminder Lists

EventKit defines the class EKCalendar to represent a calendar. Events are represented with the class EKEvent. Reminders are modeled with the EKReminder class. EKEvent and EKReminder are extensions of the abstract class EKCalendarItem. Rather than defining a new class to contain a list of reminders, Apple chose to leverage the EKCalendar class as the container of reminders. Conceptually, Reminders calls these Reminder Lists, but internal to the event store, they are just instances of EKCalendar.

EKEventStore defines two properties to access default the calendar and reminder list.

1
2
EKCalendar *defaultCalendar = [store defaultCalendarForNewEvents];
EKCalendar *defaultReminderList = [store defaultCalendarForNewReminders];

If you want to retrieve all calendars or reminder lists in the event store, you use the calendarsForEntityType: method, specifying the EKEntityType you want.

1
2
NSArray *calendars = [store calendarsForEntityType:EKEntityTypeEvent];
NSArray *reminderLists = [store calendarsForEntityType:EKEntityTypeReminder];

There’s another method, calendarWithIdentifier:, that fetches a specific calendar, assuming you know the calendar’s unique identifier.

Reminders

To create a new reminder, use the EKReminder class method reminderWithEventStore:. In order for the reminder to be valid, you need to provide values for the title and calendar properties.

1
2
3
EKReminder *new_reminder = [EKReminder reminderWithEventStore:store];
new_reminder.title = @“New Reminder;
new_reminder.calendar = [store defaultCalendarForNewReminders];

To specify a start date, you use the startDateComponents property. To specify a due date, use the dueDateComponents property. These two properties are of the NSDateComponents(doc) class, not the NSDate class.

There are two EKReminder properties associated with reminder completion:

  • completed a BOOL flag
  • completionDate an NSDate instance marking when the reminder was completed.

Setting on of these properties will adjust the other. For example, if you set completed to YES, then completionDate will be set to the current date. Setting completed to NO, sets completionDate to nil. Conversely, setting completionDate to a date will set completed to YES; and setting completionDate to nil will set completed to NO.

To save a reminder, call the saveReminder:commit:error: method. To delete a reminder, use the removeReminder:commit:error: method.

1
2
3
4
5
NSError *error;
BOOL success = [store saveReminder:reminder commit:YES error:&error];
if (!success) {
    // Handle failure here, look at error instance
}
1
2
3
4
5
NSError *error;
BOOL success = [store removeReminder:reminder commit:YES error:&error];
if (!success) {
    // Handle failure here, look at error instance
}

In both cases, the commit flag is used to tell the event store whether to apply the save/delete immediately, or queue the action as part of a batch. If you pass commit:NO, then the changes will not happen until you invoke the EKEventStore method, commit:.

1
2
3
4
5
NSError *error;
BOOL success = [store commit:&error];
if (!success) {
    // Handle failure here, look at error instance
}

Retrieving Reminders

To retrieve your reminders from the event store, you use the fetchRemindersMatchingPredicate:completion: method. Predicates are objects that encapsulate the conditions to perform a search or query. Even though we use an NSPredicate, we can’t construct our EventKit predicate by hand. We have to use one of the three predicate construction methods on EKEventStore.

  • predicateForIncompleteRemindersWithDueDateStarting:ending:calendars: find all incomplete Reminder items within a date range, for a given array of Reminder Lists.
  • predicateForCompletedRemindersWithCompletionDateStarting:ending:calendars: find all completed Reminder items within a date rage for a given array of Reminder Lists.
  • predicateForRemindersInCalendars: find all Reminder items for a given array of Reminder Lists.

To fetch across all reminder lists (calendars), you can simply specify nil for the calendar array.

1
NSPredicate *predicate = [store predicateForRemindersInCalendars:nil];

For the predicate generator methods that take date ranges, you can use the NSDate class methods distantPast and distantFuture to fetch all reminder items.

1
NSPredicate *predicate = [store predicateForIncompleteRemindersWithDueDateStarting:[NSDate distantPast] ending:[NSDate distantFuture] calendars:nil];

Once you have your predicate, you can perform your fetch, with a completion handler block.

1
2
3
4
5
[store fetchRemindersMatchingPredicate:predicate completion:^(NSArray *reminders) {
    for (EKReminder *reminder in reminders) {
        // Process each reminder here
    }
}];

The completion handler block is send an array of reminder items that match the predicate. This fetch is performed asynchronously and doesn’t need to be dispatched to another thread. However, the completion handler block is invoked on the main thread, which caused problems for me (I’ll get to that in a little bit). As with calendars, there is a method to retrieve a specific reminder if you know its unique identifier, calendarItemWithIdentifier:. You probably won’t use this method as it’s unlikely that you’ll have an item’s unique identifier handy.

Alarms, Recurrence, and External Changes

I’m not going to cover these features in any detail, since I don’t need them for my command-line application. I’ve provided some links to the Apple documentation.

  • Alarms: You can set an alarm for a given reminder, which can be time or location based.
  • Recurrence: You can set a reminder to repeat.
  • External Changes: As I mentioned earlier, odds are your calendars are tied to a server somewhere. As a result changes from the outside need to be reflected in you EventKit application.

Coming Up: Building Our Command-line Reminders Tool

Ok, that’s a pretty good review of the EventKit framework. My next post will use this information to build the command-line tool.

Comments