Transcript
>> 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, EKCalendar.
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.
Fair trade.
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.
Glen.
[ 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.
All right.
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.
All right.
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.
All right.
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.
Okay, great.
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.
All right.
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.
Okay, great.
So it looks like we're done.
And, phew.
It builds.
That's good.
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.
Okay, great.
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?
Dispatch_async.
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.
Forget it.
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.
>> 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.
Whoops. Okay.
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.
All right.
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.
All right.
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.
All right.
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.
All right.
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.
Thank, Ed.
[ 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.
Glen.
[ 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.
All right.
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.
All right.
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.
Okay.
[ Applause ]
All right.
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.