Event Kit Manager
Apple’s EventKit
can be a pain to work with. There’s specific logic that you need to make sure that you follow, so your app handles the permissions properly.
The easier way to do this is to wrap the EventKit
functionality in a manager that will manage the requests for permissions and ensure that the actual calls to the services are done properly.
All of the code for this blog post is in this sample code repo.
There are some challenges with using using the EventKit. Not the least of it, is that you need to follow Apple’s guidelines to make sure that you request access properly for both events, reminders, and optionally their calendars, so that you can create the appropriate calendars, as well as read and write events and reminders.
Requesting Access
There are a few facets of this. Working from the inside of your project out:
- Add the appropriate privacy descriptions for the calendar and/or reminder access to your app’s
Info.plist
. - Within your
EventKit
retrieval logic, make sure that you’re requesting access and if you don’t receive access, communicate that to your user and end your retrieval. - Once you have access, retrieve the calendar and then with the calendar, you can retrieve
Event
orReminder
objects.
For my sample code, I asked for full access, as you can see in both the screenshot or by perusing the sample code.
Apple uses that description as it presents the request for access to the user, as you can see below:
Now, let’s walk through the logic of my EKManager
which will wrap the EventKit
.
Access Wrappers
The access wrappers are simple to write:
We use the event store and then through that, we request access for either event/calendar access or reminder access. I illustrated the event/calendar access, but reminder access is equally straightforward.
Access Verification Logic
Within the places that need to access your events or reminders, the logic also follows, though it may be repetitive in the various places you use it.
Retrieving Data
Before you can retrieve calendar events or reminders, you’ll need to retrieve the appropriate calendar for those EventKit
entities.
Retrieving Calendars
Once again, Apple makes that easy for developers by having an API that is easy to understand. We can retrieve all event or reminder calendars with the eventStore.calendars(for: .event)
or eventStore.calendars(for: .reminder)
, then the developer can pick the one that they want.
Or if the user would like to just go with whatever calendar that the user already prefers to use, you can just grab the defaults using:
eventStore.defaultCalendarForNewEvents
or eventStore.defaultCalendarForNewRemindres
.
Creating Calendars
Or you could create your own calendars. Myself, I prefer to create my own calendar, so that any events will be branded in a "TaskManager"
calendar for events and reminders, which will make it easier for myself in terms of testing or with an eye towards removing items in mass, if a user requests it.
Gotchas
A newly created calendar will need at least a title and a source.
Finding the Source
An EKSourceType
can represent a variety of sources. Usually, it’s a good idea to leverage what the user is already using, if possible.
The logic is basically, use the same source as the default calendar for that entity, if possible. Otherwise, try to see if iCloud is possible for the user. If that doesn’t work, try to see if local access is available.
Retrieving Events or Reminders
There are two ways to retrieve event or reminder items. Either those that match a specific predicate or by trying to retrieve a specific event or reminder item by their unique identifier.
In my initial code, I went with the more generic to retrieve all events and reminders for the TaskManager calendar that I created above.
The getEvents()
function creates a predicate to search for calendar events within a known calendar for the next month. Start and end dates can be reconfigured to work best with individual requirements or for any calendar that the user has access to.
The getReminders()
function is a bit easier, as reminders are not required to have dates, so the predicate only checks against a specific reminder calendar.
Gotchas
Most of the EventKit
functionality has shifted from completion handlers to the more current async/await model. Unfortunately, fetchReminders(matching:, completion:)
is not one of them.
However Apple has an easy to use way to convert completion handlers to async calls leveraging the CheckedContinuation
interface.
By using withCheckedContinuation
, the completion handler for the call you’re making can be converted to an async
call that can return a value, throw an error, or just run asynchronously.
Optimizing by Retrieving Individual Items
This certainly works, but retrieving all of them to then search the full list is going to cause problems at scale.
It would be better to retrieve events and items by an ID number, so that after the first time it’s been retrieved, it can be manipulated by an individual item.
There are simple retrieval methods for that:
You can use eventStore.event(withIdentifier: id)
for calendar events.
Removing Events or Reminders
The eventStore.remove(_ event:, span:, commit:)
and eventStore.remove(_ reminder:, commit:)
functions take the EKEvent
and EKReminder
models and remove them.
The commit:
parameter with both functions indicates whether the removal happens now or later (in the case of a batched removal) when a subsequent commit is called.
The remove function wrapper and some proposed usages could help explain this a bit better:
This could be used in this manner:
Create Event or Reminder
Similar to creating a calendar, creating a calendar event or a reminder uses the same logic after making sure they have the appropriate access for calendars and reminders:
And
Updating Events or Reminders
Now that we can retrieve a known event or reminder from, updating it’s fairly straightforward:
Manual Testing
For this week, the only testing that was done was manual testing. Inside the sample code’s ContentView
, you may see the EKManager
being called directly to create reminders and events (as well as the requesting of permissions, creation of calendar, etc… that goes on behind the scenes for that to happen).
Next stop, testing the EventKit
manager without needing to test Apple’s EventKit
itself by using APIs to test our logic.