Ed Voas: Well, good afternoon.
And welcome to Session 136, Calendar Integration With EventKit.
I'm Ed Voas, and I work on Calendar for iOS.
So, in this session, you're going to learn about EventKit, what it is, and how you can use it in your applications.
So, first, what is it?
Well, first and foremost, it's a high level API and allows you to get access to the calendar data on the device.
It's split into two parts, mostly as a function of the way we built our OS.
But we have a non-UI side and a UI side.
So, in EventKit, we have all of the APIs that allow you to get at the calendar data itself, you know, calendars, events, etc. And, in the UI side, we offer a couple of view controllers which you can use to display and edit events.
It's not a low-level syncing API, though.
It's very high level.
But for anything that you modify in one of our, you know, calendars, if you happen to modify an event that's in, like, an Exchange calendar or CalDAV calendar, syncing will just happen automatically.
You don't need to think about it at all.
One thing I want to call up is that, you know, it isn't your data.
It's really the user's data.
So if you're going to modify the calendar in some way, make it painfully obvious to the user that that's what you're doing.
So I want to show a brief picture of the way this is put together.
So, ultimately, we have a calendar database; and that lives off in a protected area of the file system.
So it's sandboxed off from your application.
So how do we talk to it?
Well, through EventKit, what we do is we actually talk to a daemon called iCalX SD.
And that's the thing that actually talks to the database.
iCalX SD and the sync daemons are pretty much the only things that are allowed to talk to the database directly.
So let's start delving into some APIs.
So here's just an object diagram showing all the major players that we have here.
EKRecurrenceDayOfWeek on the bottom there, I'm not going to talk about.
But I'm just showing it here because it's one of our classes.
So the first one I want to talk about is EKEventStore.
This is basically where it all starts.
It's your connection to the database.
When you instantiate one of these objects, you are effectively opening the database.
And, just like the slide a couple slides ago, because we're talking to a daemon, it will actually spin the daemon up on demand, if necessary.
Most of the time, it's probably running.
But just be aware it's not exactly a lightweight operation.
So, generally, you want to have these objects around for as long as you can.
The other factor in this is that, when you get objects out of this EKEventStore, they're tied to this EventStore.
So if you close it, if you release it and then you try to open it back up again and save an event, for example, you won't be able to do it.
They're tied to the EventStore from which they came.
And to get one alloc init and you have an EventStore.
Hooray. So now we'll look at Calendar.
So, obviously, we support multiple calendar types on iOS: Exchange, CalDAV, MobileMe, etc. Again, sync is automatic for these types.
We support read-only versus read/write calendars.
So most calendars are read/write.
You can add events to them and change the events that are in them.
But we also have read-only calendars; and those would be such things as Subscribe calendars or the Birthday calendar, which is a new calendar in iOS 4.
One thing that I want to point out is you can't create new calendars in this release.
It's definitely a limitation, but just be aware of it.
We also have the notion of a default calendar.
And it usually sets this in settings, as you can see here.
If they have more than one calendar, they can set which calendar is the default.
And when you're in, like, the Calendar application and you add a new event, this is the calendar that it's automatically set to.
And you have access to this calendar.
So, to get a calendar, it's really simple.
First off, you just create an instance of EKEventStore at some point in your application.
Using that EventStore, you just call store.calendars.
And now you have an array of calendars.
Or, if you want the default calendar that I just talked about, you can call defaultCalendarForNewEvents.
Once you have your calendar, you can get information from it: title, color, type.
Type would be something such as Exchange or CalDAV.
And, then, if you to want find out if it's read-only, you can query the allowsContentModifications property.
And if that returns yes, it's writable.
[ Pause ]
Okay. Now let's start talking about EKEvent, which is the core of most of this.
An instance of EKEvent represents an occurrence of an event.
So if you have an event that repeats weekly, each one of those events that would occur is represented by an individual EKEvent.
And, from there, you can get and set most of the properties that you're intimately familiar with, probably from using the device.
So, obviously, you have title, location, start and end date.
You can set whether it's an all-day event.
You can set how it repeats, alarms, calendar, availability.
We also obviously, notes.
You can't see it because it's scrolled off screen.
But we also allow things like status.
You can get the status of an event.
You can find out if it's canceled.
So creating events is dead simple.
Obviously, we start with our EventStore again.
We just call EKEvent eventWithEventStore.
So now we've created a new event.
It's bound to that EventStore.
And then we set some data.
So title, start date, end date, and calendar.
And here I'm setting the calendar to be the defaultCalendarForNewEvents.
Now, startDate, endDate in Calendar are required.
So if you're going to try to save one of these events, you must fill these fields in.
And, if you don't, it will actually give you an error; and it will probably tell you exactly what you didn't fill in.
And, to save the event, you call EKEventStore saveEvent span error.
The span, whenever you're saving a new event, really doesn't matter what you pass there.
But it does matter when you modify and remove events.
And we're going to see how it affects things later on.
Every event in the database is has a unique identifier.
So it's unique across the entire database.
It's only valid if the event has actually been saved into the EventStore.
But they can change.
If you happen to move an event across calendars, specifically, it's across accounts, but we don't expose accounts right now.
But if it moves calendars, you might want to refetch the ID to make sure that you have the latest ID for that item.
Now, of course, it goes into the calendar, anybody could have changed that, any other app.
So maybe the user saw it in calendar and did something with it.
In that case, you might not have you might not be able to find that event anymore, because now it's been changed.
And, if that's the case, you might want to resort to sort of a backup plan and just save off some identifying information about the event and search for it later.
And to get the eventIdentifier, it's just .eventIdentifier.
We're clever that way.
Deleting events, pretty much just as easy as saving events.
So we have our EventStore.
And, then, here we're actually going to look the event up by its identifier that we just talked about.
So EKEventStore eventWithIdentifier, pass the identifier, get an event.
And then you just call removeEvent span error.
And, again, the span is meaningful mostly for repeating events.
But, for single events, you can just kind of pass anything.
This event is usually the most common thing to pass.
Okay. Every event can have an alarm.
The alarms are generally relative to the start date, and you specify them in negative seconds.
They always display the standard calendar alert right now.
So enjoy it.
One thing to note is that different calendars can have different limits to the number of alarms that they can have.
For example, a CalDAV calendar can have pretty much infinite alarms.
And an Exchange calendar, we only have one alarm.
And our UI enforces this, but we don't currently have a way to expose that through our API right now.
So it's best to assume that there's just one that you can set, at least for the duration.
Sometimes there's confusion between alarms and UILocalNotifications.
Like when EventKit was first kind of introduced to developers, people thought, Oh, I can use that to put up an alert and, you know, then I can call my application later.
And that's not what it's used for.
I mean, if you're going to if you're going to make an alarm, you're going to first off, you're going to have to make an event.
That event's going to be in the calendar.
You're just going to see that.
They could delete that.
And, then, when you press the Action button on that alert that comes up, it's going to go and show you the event, which is probably not what you want.
If you want something more general, you should be using UILocalNotifications.
And they don't involve the calendar, and the Action button will just call your app.
So, if it doesn't belong in the calendar, you shouldn't be using EventKit for it.
To add an alarm, it's pretty simple.
To create one, you just say EKAlarm alarmWithRelativeOffset.
-900 in this case means 15 minutes before the start of the meeting.
Call EKEvent addAlarm.
And, then, again, our favorite saveEvent span error.
Okay. Now we're going to touch on participants.
A participant is basically either the organizer or an invitee of an event.
With these objects, you can check to see what their status is.
You can see whether they've accepted the meeting, whether they've declined it, or maybe they've just marked it tentative.
But not all servers tell us the information, so you might get unknown.
So, if you have an event that's on Exchange 2003 or 2007, you won't be able to get that data.
If it's 2010 or later or if it's on CalDAV, you will get that data.
So just be aware that sometimes you might not always get the data.
Once you have the participant, you can get an Address Book record.
And we do that by email lookup.
So it's kind of a loose coupling.
It's not really very tight.
So it does a pretty decent job, though.
And they're read-only in this release.
So you can't modify a participant nor can you set participants on an event.
And the implication there is it means to, at least programmatically, you can't create invites in this 1.0 release of the API.
You can through the UI.
If you use our UI, it does support invite creation.
But, right now, we don't have the APIs available to you.
And to get at these things, it's just event.organizer, event.attendees.
Simple. All right.
So now we've kind of covered the basic the basic concept of everything.
So, you know, now you want to see what can we do with the APIs that we've already seen in a real application.
And with me to help do that is Glen Steele.
[ Applause ]
Glen Steele: Thanks, Ed.
So, so far you've seen most of the basic APIs that are available to you as developers in EventKit.
But, of course, we want to flesh those out in some examples for you.
So we're going to show you a demo.
And, for the purpose of this demo, I'd like you to imagine for a moment that you're a loan shark.
And, for the parents among you, there's no imagination necessary.
But, for our purposes, just think of yourself as a loan shark.
And you're no fool, so you want to keep track of these loans that you hand out to people pretty closely.
So the good news is that there's an app for that, and we can help you.
And it's called Sharkster.
So I'm going to show it to you now.
Okay. So I'll just launch it.
And this is a table-driven application.
And it's created using it's backed by Core Data.
And it's pretty simple.
We can just tap on any one of our loans here to see the details of the loan.
We get the contact of the person, the amount.
And we can drill down a little bit further and see the payment schedule and how much each payment it is each payment is and when it's due.
So we can back out here and hit the plus button if we want to create a new one.
And we can choose a contact if we want.
And then choose the amount and, you know, some whatever interest rate we want.
And then we can pick the payment schedule, you know, whether it's daily, weekly, monthly, what have you.
And then save that.
And, then, to delete, it's just a simple, you know, swipe whoops swipe to delete paradigm or edit or what have you.
So that's the basics of the app.
And so now I'd just like to show you a little bit about the Xcode project that makes this up.
[ Pause ]
Okay. So here we are in Xcode.
And I'll just show you a few things.
The first thing is, in the resources here, let's take a look at the data model.
As I said, this is a Core Data driven application.
So we just take a look.
What I want you to see here is just basically we're dealing with loans and payments.
And we have NSManagedObject subclasses that deal with these and we pass these around.
So the loans have a one-to-many relationship with the payments.
And so, if you see, these are what we're moving around.
And, then, in our supporting classes folder here, this is just basically what makes up the structure of the application, the table views and the cells and etc. And, then, in EventKit stuff, we have this thing called a Loan Event Scheduler.
And so this represents the funnel point for us for all our interactions with EventKit.
And this kind of thing may or may not make sense for you in your application.
But, for us, it's a nice way to just sort of coalesce all our EventKit interactions into one place.
So what are we going to do with EventKit to make our app a little bit better?
Well, the obvious thing is, in our payments, we'd like to be able to schedule each one of those into the calendar.
And so the place to do that is when the user taps Save, that's when we go and create those payment objects.
And we'd like to actually put those into the calendar as they do that.
So saving happens in our Loan View Controller in the aptly named save method.
And this thing is pretty simple right now.
All it does is call this generate payments helper method.
And that thing goes out and creates the individual Core Data payment objects and calculates the date on which each one of those is going to land and then and puts those into the database and relates them back to our loan object.
And, then, once we finish doing that, we just call the delegate, which basically has the effect of dismissing us and calling a save on the database.
So how are we going to modify this thing to add scheduling?
Well, let's go ahead and implement a new save method.
And this is it here, and I just left the old one up for comparison.
So we're still going to generate those payments.
But, before we do that, we're going to create one of our loan event schedulers.
Once we do that, we'll call schedule all events for payments and pass in the ordered payments.
And the job of this thing is to go in and basically create all the EKEvents for each one of those individual payments.
Then it's just a matter of releasing releasing that and then we still call the delegate.
So I'm going to delete that old method and save this.
And, then, now we're going to go ahead and delete this new method in our event scheduler.
So let's declare that.
And I'm going to create an instance variable called EventStore.
So this is our EKEventStore.
And as Ed mentioned, this represents our connection to the calendar database.
So now that we've done that, let's go and initialize this thing.
And, then, don't forget to release it.
And now we've got all our setup out of the way, we can actually go ahead and implement this new method that we have.
So, as I said, it's called schedule all events for payments.
It takes an array of payments.
And what we're going to do is just iterate through each one of those.
And, as we do that, we're going to create a new EKEvent.
So we do that with EKEvent, eventWithEventStore.
And then we set the properties on that new event.
Now, as Ed mentioned, the required ones are the calendar, the start date, and the end date.
So we make sure we set those.
And we're just going to use the default calendar for new events, which is that property on the EventStore.
And that's the default calendar that the user sets in Settings or is just provided automatically if they only have one calendar.
So, once we've done that, we can set the title.
And we've just got a helper method that returns the string for the title and includes basically the contact name and the amount for that payment.
And that's it for setting those properties.
But what we'd also like to do is set an alarm so that, you know, when we wake up in the morning, we get all the alarms for the people who owe us money and we can, you know, send a muscle to go and extract them.
And we're going to do that with just a relative offset of 0; because we want those to fire, you know, right away first thing in the morning.
Okay. So now that we've set our new alarm, we can call saveEvent and providing a span and, as Ed said, we'll go into those a little more later and a pointer to an error.
And we check the return Boolean.
And if it didn't save and there's an error, in our case, we're just going to print out that error description.
You may need to do more in your app.
And, then, the last thing we need to do is save aside that eventIdentifier.
So now that we've created our new event, we want to reference that later.
So we're going to take the eventIdentifier that we created and put that into our payment object.
So now we're creating new EKEvents.
We're storing them in the calendar.
But there's one thing we're missing and that's deletion, right?
So deletion happens on that root view controller.
And, specifically, it happens here in the commit editing style for row and index path method.
So this project was created from, you know, one of our Xcode templates, and it's already populated with basically everything that's needed to remove those loans from our database.
But we want to add just one more thing here and that's to delete all the payments that are associated with that loan in the calendar.
So, to do that, we're going to create a new loan event scheduler and then call a new method called delete all events for payments and pass in those ordered payments.
So we're done here.
Let's go and declare that new method.
And let's implement it.
Okay. So, again, this is pretty simple.
We're going to iterate in a four loop through each one of those payments, get our pointer to the EKEvent using EventStore, eventWithIdentifier.
Remember we saved this aside when we created our event before.
And then it's just a matter of calling EventStore removeEvent, and we're just using that EKSpanThisEvent span for the moment.
And same thing, pass in an error, check the result, and print out or log any issues that might come up.
So it looks like we're done.
And we're going to build that and send it over to the device.
So what we'd expect to happen now is, when we create a new event, we should see that pop up in our loan table.
Should be able to look at that and then flip over to the calendar and those new events should show up for us.
Let's see if it works.
So let's create one for Claire.
We'll do 5,000 and some exorbitant interest rate.
[ Laughter ]
I know. Ouch, right?
We're just going to choose a loan length of three days, and we'll just do the three payments, 2,000 apiece.
So we saved it.
Here's our new loan.
And pops up here.
We can see the loan payment schedule.
Looks like it's going to show up tomorrow.
But, of course, the real question is: Did it show up in the calendar?
So let's flip over the calendar and, hey, presto.
There it is.
So our payment shows up there and it looks good to go.
And we can see on each payment we created that alert, and it shows that we're going to get an alert on the day of the event.
Okay. Good stuff.
So now [ Applause ]
Now we're going to try and delete that, and let's see if this works.
So we'll delete that and flip back to the calendar.
And after that snapshot loads, we see those events have now disappeared.
So that's adding and deleting simple events using EventKit.
And, to do that, to insert an event, we just create a new EKEvent using EKEventStore.
Set the properties on those events on the event and then we save using EKEvent saveEvent.
Now, to delete, it's pretty simple too.
We can fetch the event using the eventIdentifier that we saved aside and then just remove using EKEvent removeEvent.
So that the adding and removing.
Back to you, Ed.
[ Applause ]
Ed Voas: Thanks, Glen.
Okay. Now we've seen easy events.
Let's get a little harder.
We're going to talk about recurrence rules.
So recurrence rules ultimately tell you how an event repeats.
And, you know, our UI, as you can see here, does really simple rules every day, every week, etc. But the API will allow you to do far more complex things, such as the examples here.
So we're going to just do a simple one.
We actually have two init functions on recurrence rule.
We have a simple one and then we have, like, the mind-blowing one.
So we're going to go with the simple one for right now.
So let's we want to create a weekly meeting that never ends.
[ Laughter ]
So we use EKRecurrenceRule initWithRecurrenceFrequency internal end.
So, in this case, we're going to pass a recurrence frequency of weekly and an interval of 1.
So that's every week.
If we passed an interval of 2, that's every other week, etc., etc. And the end is nil.
So no end in sight for this meeting, sorry to say.
And we just set our recurrence rule; and, again, our favorite, saveEvent, span and error.
But, you know, sometimes you want this meeting to end maybe.
So there's two ways to do that.
The first is by creating well, in every case, you always create an EKRecurrenceEnd object.
But the two ways that you can do that is the first one is by dates.
You can call EKRecurrenceEnd, recurrenceEndWithEndDate, pass the date.
And, then, the second variance is a number of times.
So you can say EKRecurrenceEnd recurrenceEndWithOccurrenceCount.
So, in this case, I want the meeting to happen five times and that's it.
And just like last time, call the same init method.
But this time we're passing the end object in for the end parameter.
Fancy that way.
And then we just set the rule and save the event.
Okay. So we've kind of discussed how to get events in.
How do we get events out?
Well, we've already seen one way, and that's by ID.
So I mentioned every event has an identifier.
And you can use that by calling EKEventStore eventWithIdentifier and get the event back.
That will give you the very first occurrence of that event.
So, if it was a repeating event, it will be the very first, you know, occurrence of that.
So, if it was somebody's birthday, it's actually the day of their birth, not the anniversary of their birth many years later.
But what you're probably going to use more often than not is search for your predicate, especially if you want to show, you know, something across a date range.
So this we offer one predicate right now, predicateForEventsWithStartDate, endDate, and calendars.
So you can search across a date range and across a number of calendars.
And because it's the only predicate we have right now, if you want to do a more fine-grained search, you will have to postfilter that list.
So an example here is to just create one.
You go EKEventStore predicateForEventsWithStartDate, endDate, and calendars.
And I'm just, you know, whatever the start date and end date are.
And then I'm passing store.calendars.
So I want to search across all calendars.
You then call eventsMatchingPredicate.
That will go out, find the appropriate events, and return them as an array.
But that array has no guaranteed order.
So now you probably want to sort it.
Well, we have a convenience method for that on EKEvent called compareStartDateWithEvent.
So all you need to do is just make a mutable copy of the array and then just call sortUsingSelector passing compareStartDateWithEvent.
And now everything's in start order in start date order, which is probably the most common way that you'd want to sort them.
Now, eventsWithPredicate is synchronous.
It will block your app for as long as it takes to operate, and it could take almost no time or it could take a lot of time, depending on what you're searching for and how big your database is.
So more often than not, you're going to want to do this asynchronously.
And we don't offer any specific asynchronous function.
Instead, you know, we say, if you want asynchronous behavior, you know, use an asynchronous mechanism such as NSOperation or my favorite, dispatch_async.
So, if you're familiar with Grand Central Dispatch I think there was a session on it yesterday dispatch_async is my new best friend.
So when you're doing I'm just going to show you an example of using dispatch_async.
So, whenever you want to start some sort of task or whatever in Dispatch, all you need to do is get your hands on a queue to run it on.
So the first thing I'm going to do is what's called dispatch_get_global_queue.
I'm using the standard priority queue.
Then I call dispatch_async, passing the queue.
So you want to send a block of code over to this queue.
And what does it do?
Exactly what you already saw.
We're just going to call EventStore eventsMatchingPredicate.
Now, someplace over in the system there's this thread that has this array.
Now, how do we get them back to the main thread?
And here we just pass get_main_queue instead.
So we want to throw it back to the main thread.
And we're going to call a method on our object called setEvents:array.
It's very simple.
I mean, the last three lines could actually be replaced with something like perform selector on main thread.
But dispatch_async is far more flexible, because it doesn't restrict you to the number of parameters that you can pass to that selector.
[ Pause ]
Okay. Modifying events.
I mean, modifying events is basically you get an event; you set the properties; you save it; and, well, you're done.
Okay. And that's fine for simple events.
But repeating events get a little complicated.
When you this is where we talked about the span parameter.
When you modify a repeating event, the span parameter then really matters.
And you and we offer two of them: SpanThisEvent, SpanFutureEvents.
And they correspond to the two buttons that you see there on that standard alert sheet that we put up whenever you do modify a repeating event in the Calendar application.
They behave very differently.
And we're going to see an example of how they behave.
They're very valid.
I mean, both are equally valid whenever you change something.
However, if you change the recurrence information on an event, then only the EKSpanFutureEvents span is valid because SpanThisEvent actually doesn't make any sense in that context.
So let's take the case of simple detachment.
So if I if I save an event and I say SpanThisEvent, that creates what's called a detached event.
So let's say I want to take the event on August 5th and move it from 11 a.m. to 2 p.m. So I do that.
So what happens is we actually go out and create a new event, but we tie it to the original series; and it's considered part of the series.
It's effectively a child of the original series.
So now if I go back to July 1st and I say DeleteFutureEvents, that event will actually go away, as well.
In contrast, if you pass EKSpanFutureEvents and I say, from August 5th on, meeting's at 2 p.m. What happens is we actually get a new event.
It's a completely separate entity and has no relation to the original.
So now if I go back to July 1st and say DeleteFuture, only the events up through July 29th will get deleted.
Everything from August 5th will stay.
If I delete passing EKSpanThisEvent, all we do is set an exception date.
So August 5th, we're not having that meeting.
And now we just set an exception date and now we know not to generate a recurrence on that date.
DeleteFuture, it's very similar.
So everything, you know, after July 29th, we don't want; gone.
And so we just modify the recurrence rules to end on that date.
So it's kind of important to kind of get a feel for, like, what happens when you modify events.
Because it does change things in certain ways, especially since, like, the deletion behavior is a little different, it's important to know.
Okay. So we've talked about recurrences.
So let's see if we can actually take advantage of recurrences in our demo application.
Glen Steele: So, so far we've been creating and deleting simple events in our Sharkster application.
It would be great if we could leverage some of this recurrence stuff since, you know, loan payments typically happen, you know, on a repeating basis.
So this is where you want to think kind of carefully about how you architect your application.
And whether using recurrences makes sense or whether it's better to use simple events, it's important to remember that, for single events, you get an eventIdentifier for each one of those, right?
So it's easy to reference those later if you need to because you have an eventIdentifier that points to that specific occurrence; whereas, with recurrences, each recurrence of the original event inherits the same eventIdentifier.
So if you want to locate that later, it's a little bit more difficult because you may have to perform a search using the eventIdentifier around the specific date.
And you've got to know where that occurrence is going to land.
So it's something you really want to think about, whether using recurrences makes sense or using single events makes sense.
We're going to try and use recurrences now, though.
So I'll switch back to the Xcode project.
And the first thing we're going to do is update our save method.
So here's the old one, and I'm just going to start a new one here.
So, in the prior version, we created our loan event scheduler and then we called generate payments and then we went and scheduled the events based on those payments.
Well, we're going to change our thinking a little bit, because we actually want EventKit to do the work of scheduling the dates for us so that we don't have to.
In generate payments, we were using NSCalendar to go and figure out what dates each event needed to fall on.
Well, now we're going to have EventKit do the work for us.
So, instead of generating the payments first and then calling schedule events, we're going to call a new method called schedule payment events using recurrence for loan.
And we're going to expect that that returns to us an array of scheduled EKEvents.
And once we have that in hand, we can go ahead and generate our payments based on each one of those.
So the next thing we do is call generate payments for events and pass in that array.
And once we've done that, we have to deal with a special case.
And it's the case of that last loan payment being slightly different.
So if the loan amount doesn't divide evenly into the number of payments, we may be in a situation where the very last payment is a slightly different amount, right, to make up the difference.
And so we have a convenience method on our loan object that tells us if that last payment is going to be slightly different amount.
And, based on that, if that turns out to be true, we're going to call a method called update event with payment.
And this is very simple.
All it does is take in one of our EKEvents.
And it takes in a payment object, and it basically puts in the new properties based on or the new amount based on that payment, right?
So it's essentially just going to update the title of that event to reflect that, hey, this one's got a different amount.
Well, let's get rid of that old save method and go ahead and implement these.
So first we need to declare the scheduled payment events using recurrence for loan.
That's a bit of a tongue twister.
And then update event with payment.
[ Pause ]
Okay. So, as I said before, we're expecting scheduled payment events using recurrence for loan to return an NSArray.
So we're just going to declare that.
And once we do that, we're going to create our first EKEvent using EKEvent, event with store.
Set the properties, just as we did before; set up that alarm, just as we did before.
And here's where it starts to diverge a little bit.
We're going to create a recurrence rule.
And the way we do that is using EKRecurrenceRule.
And EventKit defines recurrences using two parameters.
It takes a frequency and an interval.
And so, for instance if you had a payment that was due on a biweekly basis, you'd have a frequency of weekly and an interval of two.
But we don't present the UI to the user in that way, right?
They can just choose, well, I want it every three days or I want it every week or biweekly or what have you.
So we just need some helper functions to translate our concept of that in the UI to what EventKit expects.
So, to do that, we've got these two helper methods, EventKit frequency for loan and EventKit interval for loan.
And I'll just show you really quickly, if the user chooses a biweekly frequency for their loan, we return a frequency of weekly and an interval of two.
And so these are just basically translation functions here.
So we've actually done all we need to do to create a recurrence rule.
But there's one thing that we need to define, and that's the end.
And we could have done that right here in this last parameter.
But I just broke it out just for clarity.
So all we do is create an EKRecurrenceEnd.
And our loan objects are able to calculate what the end date is, so we just pass that in.
Once we've done that, we set that on the recurrence rule.
And then we've got a complete recurrence rule that we can set on the EKEvent.
Okay. So we're done setting up our EKEvent.
And the next thing we need to do is just save it.
So, just as before, we're going to save our event using EventStore saveEvent.
Check for any errors.
And assuming that all goes well, now we just need to let's just lay that out a little nicer we're not quite done yet, because we've got to return that array of events, right?
So we've now scheduled this in the calendar, but we our caller is expecting an array of scheduled events back.
And so how do we do that?
Well, we've got to find them.
So, to do that, we create an NSPredicate.
And, as Ed mentioned, EventStore has a prebaked predicate for us.
So we just call predicate for events with start date, and we provide the start date of the loan and the end date of the loan and the calendar that we want to search.
So once we call EventStore eventsMatchingPredicate, basically, what we're going to get is every event between the start date of the loan and the end date of the loan, which is not what we want.
So what do we need to do?
We need to postfilter this array.
So we created a mutable copy of it immediately so we can just filter that in place.
And, to do that, we create another predicate.
And we're going to search based on the eventIdentifier that we got given when we created that first event.
So, remember, each recurrence of the event has the same eventIdentifier as the first one.
So if we can say, hey, our recurrence lands on this date and it's got this identifier, we'll get the one that we want.
That gives us an unsorted list.
So now we need to call sortUsingSelector, and we have that prebaked selector called compareStartDateWithEvent.
And that will give us an ordered list of our scheduled payments, which we can then use to generate our payment objects in our database.
Okay. So we're finished with that method, and now we just have to implement this update event with payment.
So this is very simple.
I just pasted the whole thing in right away.
Essentially, we're taking an EKEvent; we're taking in a payment; we're figuring out what the new title needs to be; and we're setting the properties on the event.
Once that's done, we just call EventStore saveEvent and check for success.
And now that we've detached this from our series, we're going to get a slightly different eventIdentifier.
So we're going to save that aside, too, in our payment object.
Okay. So this looks all good.
So we're creating events with recurrences; we're handling the case of that different last payment amount.
But there's one thing left, and we've got to handle deletion correctly.
So we need to update our delete method a little bit.
I'll leave the old one up there and paste in the new one.
And this looks just a little bit different now.
So, previously, we were iterating through each one of our payment objects, grabbing the eventIdentifier and then removing each one of those EKEvents based on that eventIdentifier.
We don't need to do that anymore, right?
We've got a single event that's recurring, maybe another event that's that last one so we just need to take care of those.
So, to do that, we're going to call value for key on the payments array.
So this has the effect of calling the event ID method on every object in that array, and that will give us an array of all the event IDs, right?
But the problem there is that we basically got a list of the same event identifiers, right?
So we need to unique those.
And an easy way to do that is to make a set with the array, and that will have the effect of uniquing all the event identifiers.
Now, once we have that, we can loop through those event identifiers, get the EKEvent using event with eventIdentifier, and then call remove.
This time, we're using the EKSpanFutureEvents parameter for the span argument so that we blow away all of the recurrences for future events, right?
Now, it's worth noting here, actually, there's a little caveat to this.
And because, when we created this recurrence we used when we updated that last event, we used the EKSpanThisEvent parameter.
When we detached that from our series, it still remained a child of that series; because we didn't use the EKSpanFutureEvent parameter.
So, actually, the first time through this loop, we are effectively deleting the entire thing.
So it's just worth noting that I'm leaving the loop in place because, you know, in case we did decide to detach using EKSpanFutureEvents or something, we want to just take care of blowing it all away.
So let's delete this old version, save this, builds.
And let's build and send that over to the device.
Okay. So now what we should expect to happen is we can create new events.
We should see them pop up in the calendar.
And, if we examine each one of those, we'll be able to see that they're actually a recurring event.
And, then, hopefully, when we delete them, they'll get taken away too.
So I'll create a new loan here.
Let's choose Claire.
And I'll just choose some random amount.
And hopefully this will give us a payment schedule that will give us a slightly different last payment, right?
Because we just want to test that functionality.
So this gives us two payments of 440.13 and one of 440.12.
So that will go through that code that updates that last payment for us.
And, then, let's hit Save.
Take a look.
And everything looks good here.
Let's jump over to the calendar and see if they show up.
And, indeed, they do.
And if I hit the Edit button here, I'll actually see that there's a repeat rule which shows us that the recurrence worked.
And let's look at that very last payment.
And we can see, yes.
That is a slightly different amount.
So that worked too.
Now, let's jump back to Sharkster and try and delete this.
So we hit Delete, move back to the calendar.
And, hey, look at that.
They all disappeared.
[ Applause ]
Okay. So that's creating and deleting events using recurrences in EventKit.
And so, to do that, you create the initial event, set the properties on that event, set the recurrence rule, and then save using EKEvent saveEvent.
And, then, to delete, you want to get the event using the eventIdentifier or a search predicate so you can do searching, and remove using EKEvent removeEvent.
But you've got to use that EKSpanFutureEvents argument to blow the whole series away.
All right, that's it.
[ Applause ]
Ed Voas: Thanks, Glen.
So right now we've been operating in a vacuum as if you're the only person on, you know, on the device who's modifying the calendar database.
And you're not.
So, when other people modify the database, you need to be told.
And you need to react.
And we do that by sending you a notification called EKEventStoreChangedNotification.
Again, we're very original with our naming.
It can happen at any time.
And even though, like, calendar might be suspended, sync is not.
So if something happens and sync decides that it needs to bring something down into the database, you need to be informed.
You need to react to that.
They're very coarse-grained, though.
It barely tells you that something happened but you have no idea what.
Welcome to my world.
And they're coalesced.
So if you happen to be suspended and stuff changes out behind your back, when you resume, you'll get the event.
So when you receive it, what you should do is effectively treat your EKEvent and EKCalendar things as invalid.
So all the food in the fridge is bad; throw it out and go shopping.
So that's what you want to do.
So but we also have a method called EKEvent refresh which you can use.
It returns a Boolean and tells you whether the event is still valid or not.
And, after the database is changed, if that event had happened to have its data updated, it will repopulate the properties of that event with the latest values, unless you've modified something.
So, if you've modified title and location, those will stay intact.
But everything that you haven't modified will get reloaded.
But it's kind of a pricey call.
And you don't want to call it on hundreds or thousands of event.
It's just not worth it.
So the rule of thumb that we use is, generally, if you are actively viewing or editing an event, you call EKEvent refresh.
And if you're not, just refetch your events.
Okay. So that's all of the non-UI side of things.
So let's look at the two view controllers that we provide for you.
The first is Detail View.
You've seen it before.
EKEventViewControllers, how it's exposed to you.
It's the same view you've ever seen.
It does everything that you've seen it do in Calendar.
So you can respond to invites, the whole thing.
It can allow editing, optionally.
You have the Edit button up here.
And it listens to those notifications we just talked about for you.
So, if the event changes, it will just refresh itself.
If the event is deleted, it will pop itself off of the navigation stack.
To put one up, really easy.
Just alloc and init, set the event, tell it whether you want editing or not, and then call pushViewController animated.
And now you have Detail View.
Likewise, we supply you with the standard editor.
And this, again, the exact same editor that we use in the Calendar application.
It's exposed through EKEventEditViewController.
So you can edit existing events or it can use this to create brand new events.
To do that, you could just not pass us an event and we will make one, or you can pass in a partially constructed event.
So consider the case of data detectors.
So you tap on a date in Mail, and it has some certain fields that it wants to prepopulate the event with.
It just fills that in and just invokes this view controller with that information.
If you don't pass us anything, we will fill in defaults as needed.
So we'll fill in a default date, start and end time; and we'll also always use the default calendar for new events that we keep talking about.
It also relies on a delegate.
It will tell you when the user has canceled or saved the event or whether the event was deleted.
And deleted can come from two sources: either the user actually deleted it, or we picked up one of those notifications we talked about, determined that the event had been deleted, and we want to tell you it's gone now, please close me.
We also allow you to override the default calendar that we'll use.
So you might just supply a calendar before you, you know, pass it into this view controller.
But let's say you did that and now sync came along and decided, You know what?
You went and deleted that calendar on a different device, and it's gone now.
So what do you do?
Well, we'll call the delegate and say, well, we need a new default calendar.
What should we use?
And if you don't fill if you don't actually override this method, we'll just use default calendar for new events.
But it's there if you want to hook it in.
Explain the editors, it's just as easy.
Create it and just set the event in the EventStore this time.
And then you set edit view delegate to whatever is appropriate for your application.
And then it's packaged as a UI navigation controller, and it's meant to be presented modally.
So you want to call presentModalViewController animated.
And, likewise, when you want to dismiss it, you just dismiss modal view controller.
Okay. Let's just see a quick sample of actually using Detail View in our application.
[ Pause ]
Glen Steele: Okay.
So, really quickly, we're just going to add the EKEventViewController to our application.
And so we want to do this in the list of payments that we have.
We'd like to be able to tap on one of those and get shown, basically, the Calendar UI.
So that view controller is actually the payment schedule view controller.
And the first thing we want to do is just update our configure cell method.
We want to make sure that we show that little disclosure chevron.
So, to do that, we just set the cell accessory type to UI table view cell accessory disclosure indicator.
And the next thing we need to do is update our did select row at index path method.
So what we're going to do here is grab our payment object that's associated with the payment that we just tapped.
And we do that using our core data magic and our fetch results controller.
And then we need to create a loan event scheduler so that we can actually find the EKEvent that's associated would this payment.
So we're going to define a new method called event recurrence for payment.
And that's going to return our EKEvent.
Once we've got that, we can create our new event view controller and then set the properties on it, the most important one being the event.
And set the allows editing property.
Now, you know, I had this set as no.
But I'm going to set it to yes just so that you can see how easy it is to put the Edit button up there and get the edit view controller.
And once that's done, it's just a matter of pushing it onto our navigation controller.
So we're done here.
Let's just go and implement this event recurrence for payment method.
[ Pause ]
Okay. So this is very simple.
Actually, you've seen this code before.
This is the code that we use to search for events once we had created the recurring event, and it's pretty much the same thing.
All we're doing is creating a search predicate, calling eventsMatchingPredicate using a mutable copy to postfilter in place.
And, then, we know that we're going to get basically one event, right?
So we're just going to take the first event and return that.
So let's build.
Everything went okay.
Send that over to the device.
And now what we should expect to see is we should be able to create a loan and look at our payment schedule controller and tap on any one of those payments and we should see EventKitUI for each one of those.
So let's go ahead and do that, create a new loan for Charlie this time.
We'll be a bit more ambitious.
And let's do it over a month and do payments every let's say every three days.
Hit Save. And now, if we tap on this and look at the payment schedule, it looks like our chevrons showed up.
And we should be able to tap on any one of these and, hey look, we get the event UI for it.
And we can tap on the Edit button and that gives us the event UI edit controller.
[ Applause ]
So that's using EventKitUI to show EventKitUI in your application.
And it's pretty easy.
You just need to find the event using the eventIdentifier or search for it using the predicate methods; create a new EKEventViewController; and then set the controller properties, mainly your event property; and push that onto your navigation stack.
All right, thanks.
Ed Voas: Thanks, Glen.
Okay. So that's pretty much the bulk of the API.
It doesn't really take a lot and you can still get a bunch of stuff done.
I just want to mention some facts about working with the simulator.
So, in the simulator, the SDK, we don't have the Calendar application there.
So it's a little difficult to manufacture events.
So one way that you could do that is just write you know, we saw how easy it is to generate events.
You can just write some code and just populate the database that way.
Or you can actually use the birthday calendar which I believe does work in the simulator.
So you can just add some contacts, give them birthdays, and the birthday calendar will just autopopulate.
And that would be an example of a read-only calendar that you could poke around with.
So the bottom line here is we have, like, really easy to use, very high-level APIs that allow you to get at calendar data on a device.
We have a couple of view controllers.
They do everything that you can do in the Calendar application.
And one of the things that we don't demonstrate is that they're fully rotatable now in iOS 4.
And, obviously, this is just the first release of this API.
We want to do more stuff as time progresses.
But it's up to you to help us to figure out what we should do first, all right?
So the best way to do that is to file bugs.
Now, many people file a bug and say, I need an API that gives me X.
It's like, Okay.
But what are you really trying to do?
So if you could tell us, you know, exactly what you're trying to get at, you know, what you're trying to do with the API, we can then see how we can best fit that into the public API.
And we take all of those requests; and, obviously, the most popular features, the most oft-requested things are the ones that we prioritize first rather than last.
So tomorrow there's a lab at 9 a.m. God help me Lab B.
Yesterday, there was a talk on GCD.
So, if you missed that, you can catch it later on in the on the web, I guess.
There was also a session which I neglected to put here on UILocalNotifications which you might also be interested in.
If you need to contact someone, I pick Mark Malone.
So if you have questions on Calendar, you can contact him.
We also have documentation on developer.Apple.com.
And the on the Dev Forums, I'm patrolling that, too, and trying to answer questions as best as I can.
So you can always use that as a way to talk to us.