Integration • 56:20
Mac OS X Leopard provides two powerful Cocoa frameworks for accessing a user's contacts and calendars, which are frequently synced between Mac and iPhone. The Address Book framework lets you talk directly to the data behind Address Book, and provides a reusable panel for choosing stored contacts. The Calendar Store framework gives simple read/write access to events and tasks that appear in iCal, as well as the ability to create complex recurrence rules for repeated events. Find out how these two frameworks can help you personalize your Leopard application while saving you hundreds of lines of code in the process.
Speaker: Matt Drance
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it may have transcription errors.
Good afternoon. It's Friday. Everybody tired? Good. Well, I'm pretty tired too. So welcome to the last session of the week, Accessing Contacts and Calendars on Leopard. My name is Matt Drance. I'm the Sharing Technologies Evangelist here at Apple, and it is my pleasure to play us out. I don't know what that means, play us out. Does it mean end the show? I don't know.
Somebody got it. Thank you. So we're going to be talking about two really critical frameworks to providing an excellent user experience on Mac OS X: the address book framework and the calendar store framework. These two frameworks are important to you as a developer, but they're also important to the applications that Apple writes to, and you've seen their effect in multiple places. The address book framework helps you add personal contacts to data that would otherwise be arbitrary and confusing. Here's a screenshot of iChat with a bunch of random screen names. And iChat uses the address book framework to take this overwhelming data and turn it into something that an average person can immediately recognize. First name and last name, picture. Now I just take one glance at this window and I immediately know really what I'm looking at.
Likewise, the Calendar Store Framework allows you to take pieces of data that are around in the system and give the user an effective hook into the Calendar Store Framework that's used by iCal and a bunch of other applications on the system. So Mail is one of these clients. And Mail's got some pretty clever data detectors that will allow you to just create an event right out of the blue.
And you also get a lot of additional perks by working with these frameworks. All of the data that you read and write using Calendar Store or Address Book are automatically synced to MobileMe and before that,.Mac. And of course, all of this data also is synchronized to the iPhone.
And like I said, working with this data is important to providing a consistent user experience on the Mac, particularly because the standard has been set, the bar has been set by the Apple applications. These are just a few. Dashboard, iChat, Safari, iPhoto, iTunes, and Mail. Pretty much every application on the system has some hook into Address Book and iCal so users can stay connected and stay informed. And it's a challenge for you as a developer to make sure that you're doing the same thing, that you're living up to that standard. I mean, this is really the difference. Anybody can put a window on the screen and launch an app and work with some data, but it's stuff like this that propels you to popularity in the community and things like winning an Apple Design Award. There's a lot that goes into winning an Apple Design Award, but personalization and familiarity and consistency with other areas on the platform are a big part of that.
So let's go ahead and get started with contacts. And as I already said, contacts are provided by the address book framework. And the first thing the address book framework gets you is free persistence. So if you're working with an app, if you're writing an application that does contact management or project management and has either a trivial or a critical connection with contact data, if you use the address book framework, you don't need to worry about persisting that information. It's all taken care of for you. And we worry about scale and performance and redundancy and consistency and all that.
And most importantly, it gives you integration with the Address Book application, which is more than just the double-clickable app that you see on the file system, but additional related services like Spotlight. If you write contact information using the Address Book framework, those contacts are now searchable in the Spotlight menu. Likewise, the Address Book dashboard widget has that data. And again, applications like Mail and iChat. Also use this data whenever it's available for a better user experience.
So the details of the address book framework. We're going to stay pretty high level here, because we've got two frameworks to cover in one hour. So I'm going to give you an overview of the object model. Specifically, there are two main types of data that you work with in the address book framework-- people and groups.
We've got structured search elements that you put together when you're looking for specific pieces of data and you want to get a real person, a vCard out of that. And we also have a reusable UI element that you can use to provide a really consistent experience from what you see in mail and iChat and address book.
So the object model is very simple. There is a base class called AB record that has the core pieces of information, like a unique ID, as well as a hash of properties and keys and values for things like first name, last name, birthday. And then the stuff that you'll actually be working with, you're not going to be really working with AB record directly, is AB person, which is a subclass, of course. And it has more things that are attributed to people as opposed to records in the database, like an image, as well as groups that the person belongs to.
And we also have groups. And if you want to just go ahead and launch the address book application, you can see what we're talking about. The groups are on the left. People are on the right. And groups are straightforward. They've got a list of members and a list of subgroups. So we can have hierarchical groups in the address book. And you have access to all of this stuff through the framework. So because we're limited on time, we're going to concentrate on people today. And I think that's probably what most people are interested in. Anyway.
So the AB Person class gives you most of this personal information that's going to be used in various places. And these are all stored as keys in that key value dictionary. So we've got things like first name, last name, the person's birthday, email addresses, AIM or Jabber IDs, home pages, as well as physical address. And those are just a few. There's tons of information available to you through AB Person.
So let's get into the code. It's really straightforward. To read information out of a given person, you start by importing the address book framework. So those of you who are new to the platform, you either drag or add a framework to the project. In this case, it's address book. You import the address book header. You get the shared address book. This is a singleton instance that does most of the work for you.
And then we go ahead and get a person. And there are a bunch of ways to get a person. In the interest of simplicity on this slide, we're doing it using a unique ID that presumably was stored somewhere else. And we'll go over searching in a little while. And then we just start getting the information.
Very straightforward key value sort of coding. And you've probably seen this if you've gone to other Cocoa sessions like Core Data or Bindings. You do value for property, and these constants are defined in the address book headers as well as in the documentation. So properties like first name, last name are very straightforward.
And then writing that information is pretty much the same code. Instead of value for property, you say set value for property using the same constants as before. Now, this is very important. When you're done writing information in the address book, whatever information it is, you need to remember to call save on the address book instance that you got before. If you don't do this, the data that you wrote is just going to disappear as soon as you quit. So at the time that you're setting, when you set values on a person, you're just setting values on something in memory. So the save is what commits it to disk.
So first name and last name, very straightforward. They're just strings. You go ahead and get the property, you get a string. Pass in the property, you get a string back. But there are a lot of other pieces of information that can be redundant and have multiple values, like phone numbers, or addresses, or email addresses. A lot of the things that we use for communication have multiple values. So how's this data handled? It's handled with a special data type called ABMultiValue.
And these are basically typed collections. You can think of it as dictionary if you want. But the keyword is typed. So we have string-based multivalues for things like phone numbers, IM, handles, email addresses, et cetera. Multi-string is the prominent type of multivalue. We've got multi-dictionaries for things like addresses, because an address has multiple lines to it. So each address is a dictionary, and the address property is a multi-dictionary value. And of course, things like dates for anniversaries or birthdays, although I'm pretty sure most people only have one birthday.
And every value inside that multi-value has a label for things like work and home. And you can see that up here on the screen. It's also got a unique identifier. And this is handy for when you're doing fast enumeration on the values that you get out, as well as for persistence. So if you want to save a reference to a specific value, you go ahead and you iterate through, and you've decided that you found the value you want, you can save that identifier and just go pull it right back out next time. You don't have to iterate again. So how do you get at these multivalues? How do you know what you're coding before? Because like I said, you can have multi-string, multi-dictionary, multi-dates.
So how do you negotiate this? Because there's only one AB multi-value class. Well, basically you have to just look through the pieces, the information that we provide in our documentation in SDK to find out what you're going to be working with. So first thing you do is look up the property and the type. And you can do this in the abglobals.h header. So in this case, we're looking at the email property, which, like I said before, is a multi-string. So the comments in the headers will tell you that.
You can also look in our documentation, which is a little more friendly on the eyes. The AB Person Class Reference has a clean list of all the different properties that we provide by default and what kind of multivalue they are, whether it be multistring, multidate, multidictionary, et cetera. So the first thing you need to do is find out what data you're gonna get at, and that will then tell you what kind of multivalue you're dealing with. Now once you've done that, it's time to write some code.
Again, everything in AB_PERSON is just value for property. And what you get back changes based on what the property is. So in this case, we've got the email property. And it's going to be an AB_MULTIVALUE. So I mentioned before those identifiers. I'm just going to iterate through all of the values that I get back until I find the one I want. In this case, I want somebody's work email. So I check the label. And I check it against the constant for the work label. And there we go. I go ahead and store it. I break out of the loop. And I move on.
So that's great, and you've seen I'm using this nebulous unique ID here, and where did that come from? So that's not really realistic. Most of the time when you're dealing with the address book framework, you're going to have some piece of information like an email address and a ton of contacts in the address book. And how do you go ahead and find that one person that corresponds to that piece of information? This is what searching is all about. And when you're working with the address book, most of you are probably going to be doing searches.
So you do that using these AB search element objects. And it's pretty straightforward. You just call this search element for property method on the property type that you're interested in. So in this case, the KAB email property. So we're going to search for somebody's email address. and you set the label and key, usually you're gonna set these to nil. Before, like I said, we have work, home, mobile, labels. In this case, if you set those to nil, it'll basically search all of the values in the property.
And the key, of course, is for multi-dictionary. So in the case of email, email is just a multi-string. Key doesn't matter anyway. And then you specify the value that you're searching for. And go ahead and look at abtypedefs.h. And that will tell you these constants for all the comparisons. Here we've got ab=caseinsensitive. I believe we've got prefix and case sensitive. The standard matches, standard compare types that you would expect in a search like this.
So that's searching. It's the ability to pluck out exactly what you're looking for. But there's some additional nuances to using search that you need to be aware of. And there's a huge performance win here as well. Obviously because you don't have to loop through. So in the brute force sense you don't need to do it. But there's a lot going on here that you should know about. So this is kind of the brute force approach. If I was looking for a couple, I wanted to get all the emails and all the phone numbers and all the aim properties, all the aim names out of everybody in the book.
So if I was going to do this in a very straightforward way, I would just get all the people in the address book, which is hitting the disk initially to get all the records. Then I go ahead and I ask each person for-- go through each person, ask them for the email property, the phone number, as well as their instant message ID. And you notice that we got a list of people, the disk is hit again every time we request the property. That's because there's an unknown amount of data in each of these person records. So we're not just going to load the whole database into memory when you ask for the array of people. So because we're not being very smart here, we're wasting a lot of time and a lot of performance. And of course, because this is a loop, This is a serious scalability problem if you're dealing with a large address book or a networked address book that has some latency tied to it.
So search is a really good way to get around this. So rather than iterating through and just pulling the properties out, we're going to construct a compound search element to go pull this data. So I'm going to do one for the email property, one for the phone property.
and for the instant message property. And you notice what we're doing here, we're doing a not equal to nil. Basically anybody who's got an email address or a phone number or an AIM name, give those back to me. Now this is not just a search where we're going to get those people back, because the address book framework uses these search elements as a hint. So the framework now knows that this is the data you're looking for. So we're going to go ahead and preload all of that stuff when we return the matching records to you.
And there's one last step, which is to actually combine-- right now we have three search elements, and we need to combine them into one. So we put them in an array, and we create a conjunction between those search elements with a type of KAB search and. And we also have search or. So you can construct some pretty complicated search elements if you need to.
So now what happens? Now instead of getting a list of all the people in the address book, regardless of who they are and what they have in their records, we pass, we call records matching search element with that search element we just constructed. And this is the one time that the framework is going to hit the disk now, because it's got all the information, all the requests it needs. And then when we, excuse me, I was expecting another disk icon to show up, but that's the whole point. There's no step two.
When the search element returns, we've already got all the data pre-populated. So when we go ahead and we start looping through them, there's no more I/O. We've got everything we need in memory. So this is significantly faster, especially when you start to scale out, like I said before. So that's search. Search is something you're going to want to get to know very well.
I kept using the word "personalization" in the introduction. So to that end, the address book framework, of course, has image data. You've got pictures of the people-- of the friends and family that the person has in their address book. And you can get at this using the framework using one of two methods.
The first one is AB_PERSON_IMG_DATA, which gives you an NSData reference representing the bitmap image for that person in the address book. Now, this is a blocking call, So we also have an asynchronous method, begin loading image data for client, that you specify a callback for when the image data is done.
And this is probably the way you want to go, because if you're dealing with a long list of people, you don't want to block the event thread. You want to just have that stuff, wait until it's ready, particularly if, like I said, if you have a long list, and if you're dealing with something like a networked address book, which you probably don't know as the developer. The user probably knows that they've got a networked address book, but by then it's too late, because you've already written the application. So use the asynchronous method. It's not more complicated at all. And you notice this is an NSData. It's not an NSImage, but we have a constructor for NSImage that takes NSData as an argument. So very straightforward. It's just one extra line of code.
And, of course, not every person has a picture. Not everybody has a picture for every single person in their address book. So just to add a little bit more customization, a little more personalization, you can go ahead and fall back to something like the default NSUserImage that we provide to you as part of Cocoa. So go ahead and try to get the person out, search for them by name, and get the image data. If it's nil, just go ahead and throw that NSUserImage up. And let's show you what that looks like right now.
So to make this interesting, I did not write this application. This is actually a Core Data example that you can find in Developer Examples Core Data. It was written for Tiger when Core Data was introduced. And luckily, it has context and calendar information in it. So what we're going to do today is we're just going to wire this application up to the address book and iCal frameworks, which I think is more interesting anyway, because there might be people in the crowd who've already written something and are just looking at address book and calendar stores and afterthoughts. So I'm going to show you that it's pretty easy to do this, even if you didn't think of it initially. Let's go over here to People.
And, you know, I don't think I need to demo this too extensively. You create a list of people in this application. They're saved out in the document using Core Data and NS Persistent Document. But this is not very personal. It's pretty bland, actually. But it's meant to be a technology demonstration for Core Data. Let's turn it into something a little more personal. So this is the new event manager. That looks the same, of course. But you'll see that I've added this image column, and I did that in Interface Builder beforehand.
And like I said, if you have something that doesn't match the address book, you should use that default NSUserImage. So we start there with that. And let me bring up address book real quick, just so you can believe me. There's John, our good friend John Appleseed. Let's change the name here and see what happens. So I've written the name, and now I've got a picture of John. So maybe if I know 10 people named John Appleseed, I can make sure which one it is.
But this immediately adds some additional flavor to the application. It's not just a gray and white window anymore. Now we've got people, personal information here. How did we do that? It's pretty straightforward. First, as you saw-- Let me open up the nib here. As you saw, I added a column to the table. I'm not going to walk through this because we do have a lot to cover. I think I've opened the wrong project. So let's just go right over to the code.
So Core Data has pretty much a codeless data persistence system. The idea here is that you don't have to write a lot of code. And you'll see here, you can tell here by the names of the files that a lot of this stuff is things that I've added. And we'll get to the Calendar Store editions later. But what I've done is I've created this special subclass of NSManagedObject. And I don't want to have to teach you guys Core Data, too. I want to stick to two frameworks, not three. But what I've basically done here is I'm paying attention to whenever the first or last name changes in the application. And when that happens, I go ahead and I produce a search element for the first and last name.
And in the interest of efficiency, I go ahead and I wait until they both exist. And now it's just the search elements you saw from the slides. I go ahead and I check for the first name and the last name. I do a case-insensitive search. And I populate a list of address book matches based on the name that currently exists in the person.
And then this method image is what the image column in the interface is bound to. So you notice there's no GUI code here. This is just data code. And that's because I'm using Cocoa bindings on the other side to just pluck the image right out from the interface.
So if you guys, those people who are new or not familiar with Cocoa Bindings, we'll make this sample available. You can see how it works, but we've got some great tutorial on bindings. And this is it. And you notice I construct an image from the data. So you see here I'm getting the image data from the person, and then I call NSImageInItWithData. And if the image does not exist, I go ahead and I return that NSImage named NSUser. This is literally the code right out of this, pretty much right out of the slides. So this is really straightforward. This code is the entirety of what we had to write to hook that stuff up. There wasn't a whole lot of property listening or playing around that we had to do to get into that code.
And again, this was written three years ago. I just pulled this out, actually I did this this morning. You might choose to believe me or not. Two weeks I think has been the popular amount of time for demos this week, but actually this took me about two hours, including the calendar part of it. So very straightforward. Couple of minutes of work, a couple of lines of code, and we've immediately added faces to names. Can we go back to the slides, please?
So you notice that that was a document-based application. I didn't save the document out, but this begs the question of if I'm going to link myself to the address book framework and I have documents that are moving around the file system and maybe they're moving between machines where there may or may not be the same address book, it may not be consistent, how do I negotiate that? How do I make sure that the user experience is not completely broken with this document when it moves around?
So I've got some pretty straightforward advice for you. First of all, you want to save the unique ID you get. When you find a person, get that unique ID and save that. Because that's just a string. It's a really lightweight way of hooking back to that person. But you also want to save the data that's really critical and important to providing that info to the user. So maybe it's just first and last name, like in the case of this. In the case of this. Maybe it's also e-mail or maybe it's a phone number. But just little pieces of data. You don't need to save everything out like the image or, you know, other pieces of information like their 10 addresses and their birthday and the name of their best friend from high school.
and then go ahead and fetch the rest. Whatever else, whatever additional flavor you want to add, such as the picture, you can go ahead and pull that back when the document's loaded again. Now, if the ID is invalid, maybe the user deleted the person from the address book, or maybe they moved the document from one machine to another where the ID may be different due to syncing or other inconsistent changes.
obviously you construct a search element. And you would fall back to that data that you saved. So the stuff that's important to you, both for displaying it on the screen, as well as using it to look up later in case you don't have a match. So go ahead and fall back to search. And then if the search still fails, make sure you at least have something to show them. So that's why I said save the important data. So name, phone number, email address.
And there's one other thing that we want to talk about with the address book data, and that's notifications. Because this is a framework, any other number of running apps can go ahead and be modifying this database while you're a client of it. And the address book application itself could be one of them. So you want to register for notifications to these changes.
So we have notifications that include lists of updated contacts, contacts that were added, new contacts that were added, as well as contacts that were removed from the database. And this follows the standard Cocoa pattern of notifications. So you just use the standard notification center, and you go ahead and register for external changes using the constants that we provided.
But make sure you understand this is not the distributed notification center, for those of you new to Cocoa or not new to Cocoa. The distributed notification center is what you traditionally use to get notifications from outside your process. But in this case, because it's really coming from a framework that you've linked against, you always use the regular notification center, regardless of where the change actually came from.
So that's it for data access. And I mentioned before that there's a reusable UI that we provide to you as well. And this is called the People Picker, the AB People Picker View class. And this basically gives you an address book view that looks very similar to the application as well as the other applications that you've seen like iChat and Mail, just to name a few. And it has a free search field. It has access to all of the groups that you see normally in the address book. And it also gives you the ability to specify which properties you want to be visible.
So this view is actually accessible from Interface Builder. You don't have to write a lot of code to make this work. You can drag it right out of IB and right into your window. It's also fully configurable from within IB. So I mentioned before, you can choose to just show a number of properties, like the phone number and the email. Otherwise, you'll have a huge list of all the properties that are available in the address book. And things like allowing multiple selection and what the default displayed property would be. All of it's configurable from within Interface Builder or from inside code. And you can also set up a callback for when the user double clicks on a value. So you go ahead and obviously this is a people picker. You want to know when the person picked something. So you register a callback to get that information and you're done.
So that's it for the address book. Again, the address book is really the way you want you You don't need to worry about populating and saving all this data in a consistent format. The Calendar Store Framework takes care of that for you, and it stores it in an open standards format, the iCalendar format.
And of course, it gets you integration with the iCal application and all of its related services. So if you set an event with an alarm, using the Calendar Store framework, not only will that event show up in iCal, but you will get that iCal alarm for free. So you can notify the user to do something, like sign on to World of Warcraft, even if World of Warcraft isn't running.
And just like Address Book, you can search for events in the spotlight. Your users can search for events in the spotlight menu once they've been written out. So anything you create with the Calendar Store framework is, again, widely accessible from across the system, even when your app isn't running. So you're providing that value to your users, even when your app is out of their mind.
Of course, we have the iCal dashboard widget. And you'll see the events that you created for the particular date in that widget. And finally, it gives you access to the open standard CalDAV services, like our own iCal server, as well as a number of other third-party offerings. iCal is fully CalDAV compliant, so you have the ability to read and write events that are part of the user's CalDAV service using the same APIs.
So very much like the address book framework, the Calendar Store framework has a simple object model. We've got calendars, events, and tasks, or to-dos as we call them in user land. There's a predicate-based search, which is closer to the newer Cocoa style of doing searching. And there are some pretty complex recurrence rules. We've got really good support for all kinds of crazy recurrence, and we'll talk a little bit about that later. But pretty much everything you can do in iCal, you can do with the Calendar Store framework in your code. So let's start with calendars.
Reading calendars is very straightforward, and it's extremely simple, just like getting information out of the address book. You call the default calendar store. to get the singleton instance of the Cal Calendar store. And basically, you get an array of calendars, and you just go ahead and iterate through them. And we've got some types of calendars declared in constants, so in this case, I'm looking for the birthday's calendar. And I go ahead and I get that, save it out, and work with it. Really straightforward.
Creating calendars is equally similar. I just use a factory method to create the cal calendar here. And you notice the Objective-C2 syntax. So everything here is done using Objective-C2 properties. So I just set the title, the notes. I can get a UID out of it. And that's something you want to save. Because if you've created a new calendar, the UID is going to be how you get to it later if you don't want to iterate through the list again. So very much like-- Very much like the address book framework, you need to make sure you save this information when you're done. That goes for everything, calendars, events, tasks, all of it. When you're writing these properties, all you're doing at the moment is touching things in memory. We don't just hit the disk every time you set a property. Otherwise, we'd be pretty busy. So make sure you save the calendar to the calendar store when you're finished working with it.
And one thing to remember is that calendars you create with the framework are always local. They're always on the local file system. Basically, you can't create CalDAV calendars on the server because there may be administrative or permissions problems with that. So to be safe, the only thing we let you do is create local calendars on the user's machine. And for the most part, that's really all you need to do.
So a note on actually doing this. If everybody created a calendar for their own application, we'd have 100 calendars on the left side of iCal, and I'm not sure that's a very good user experience. So when it comes to organizing events that your application creates, if that's important to you, if you want to keep your events organized, you could create your own calendar. But really, you should ask the user what to do. Give them an option to choose from the list of calendars available, which you can easily get, as you saw in the previous slide. And then also give them an option to create a new calendar corresponding to your application. And then based on that selection, do either one or the other when you create your new tasks or events. So that's calendars, very straightforward. Let's move on to events. These are probably the data that you're most interested in.
accessing events is done almost entirely through searching. That's the main way that you're going to get at this data. So we get the default calendar store again. And we're going to construct a predicate. Like I said, it's a predicate-based search. So you give a start date and an end date, basically a range of time where you want to see the events for, and also the calendars that you're interested in. In this case, we're just passing all the calendars.
And then we call events with predicate, passing in that predicate that was created for us. And we get an array back of all the events that fall within that time frame. Now this is really nice because you, predicates are easy enough, but with this, you don't even need to know the nuances of predicate syntax. You just call this factory method that we provide to you, and all the work is done behind the scenes.
So where do we get this today and tomorrow from? Notice that those just magically appeared in the slide. I'm going to take a minute to digress and talk about the dates for a second because there are a bunch of ways to work with dates on Mac OS X, and this is the more modern, more recommended way. I wanted to go over it for a minute because if you're going to, for example, search for birthdays in the address book, this will also be important. So the first thing we need to do is get today.
Notice both of these dates start off as nil. And we're going to use this handy method called range of unit, which comes from the NSCalendar class, to populate these dates for us. So I'm passing in the today, and I say I want the range of a day. And then the for date, the final parameter you see there, is just a brand new date, which corresponds to right now, this second. So by asking for the start date of the day range, it's going to populate that today object with midnight today, the very beginning of the day. It's also going to give me the time interval that corresponds to that unit. So in this case, it's going to give me the number of seconds in a day. Of course, I could have calculated that myself, but when we get into months and years, then it gets more complicated with days in the year -- days in the month, which month it is, is it leap year, et cetera. So I've got the start date. I've got today at midnight.
And now I've got the time interval. So it's easy to compute tomorrow from there. I just add the time interval to the today date. Now I've got today and tomorrow. So really it's just four or five lines of code. Now there's an NS calendar date that you'll also see in our Cocoa documentation. An NS calendar date is a more antiquated piece of information that we may have actually deprecated here at the conference this year. It's been in danger of deprecation for a while now. So if you're new to the platform or you're going to be writing new code that deals with dates, this is really what you want to be working with. Keep that in mind.
So we've gone over searching and reading events. Creating events is pretty simple. Just like the calendars, we have a factory method for creating a new event, and then it's just Objective-C2 properties. I set the title as a string. I can set the start date using NSDate. And I also have to set a calendar. If I just create an event and try to save it, I'm going to get an error because everything in iCal needs to have a calendar associated with it. So the stuff you see here is basically the bare minimum for creating a new event.
There's additional stuff, like the URLs. You can set an alarm for 15 minutes before. Basically, it's a totally arbitrary value. So it's up to you to decide how far away you want the alarm to be. And then you can also set the types of alarms. And then once again, make sure you save the changes. Because again, you're just modifying an object in memory. So you have to actually call saveEvent spanError to write this out to the disk. And so you notice this constant cal span this event.
It might not be obvious immediately what that means, but remember, we have support for recurring events in iCal and in the Calendar Store framework. So when you're working with an event, you need to have the option to specify, am I changing the start time of just this one date, or am I changing it for all of them in the case of a recurring date? And it's just like the dialogue that you see in iCal. If anybody's modified a recurring event in iCal, you've seen these options. Only this event, this event, and all the events in the future, or all the events ever.
So you have those three options. And most of the time, you're probably just going to use CalSpan this event. But that span needs to be provided even in the case of non-recurring events. And it's a great segue to recurrence. Like I said, we've got pretty robust support for complicated recurring patterns. So every event in the calendar store, whether or not it's recurring, has a recurrence rule.
Well, a non-recurring event has a nil recurrence rule. But every event does have an occurrence, which corresponds to an NSDate. And it's important for you to know this because recurring events have the same UID. I'm going to say that again. Events have unique IDs, But a recurring event, every occurrence of that recurring event has the same UID. So if you want to get at a specific occurrence of an event, you need to check that occurrence property to make sure you've got the right one. And that's why it's a date. That's why the value is a date as opposed to some arbitrary serial identifier. And like I said, there's a lot of complex pattern support. So we can do standard stuff like every day, every week, every month, every year. Do things like every third Saturday, the fourth Tuesday in March, second Friday of any January and February, ending in February 2015. Pretty much anything you can do with the GUI in iCal you can do with the calendar store framework. And we're pretty strapped for time again, so I want to make sure we get in under time. I'm not going to go into the details of recurrence, but it's there if you need it.
So now let's talk about tasks. And these are known as to-dos in the user world. Getting at tasks is very similar to getting at events. You use predicates, and we've provided factory methods to create those predicates for you. So the simplest one is just all the tasks on the system. And you pass the associated calendars that you're interested in. We also have more granular things like uncompleted tasks, things that I'm not done with, or uncompleted tasks with a specific due date, as well as tasks completed since a certain date. So pretty much everything you would need to get at, we've got easy factory methods to produce the search predicate that you need.
Creating a modifying task. This should start to look familiar to everybody by now. It's very straightforward. You have your factory method again. You've got your properties to set very simply. Title, priority. We've got priorities of high, low, medium, and none. Notes at the bottom. And of course, a due date. And again, just like events, make sure you've saved this. If you create the object in memory, nothing's happened yet. Make sure you save it to the database.
So a note on CalDAV. I said that Calendar Store gets you access to CalDAV because iCal is fully CalDAV compliant. But there's a nuance here that you need to be aware of. iCal basically acts as the middleman to the CalDAV server. And when I say iCal, I mean the iCal application. So what happens when you write an event to a CalDAV calendar is that event has basically been saved to the local calendar store storage, to the local cache. And it's not until iCal launches that that new event change actually gets propagated through to the server. So it's just something that you need to be aware of. It's great that Calendar Store provides access to to CalDAV events, but they're basically locally stored copies of the CalDAV events. And likewise, your changes will be locally stored until iCal has the chance to sync those back. So something to remember.
And just like the address book framework, the calendar store framework has notifications. And we have notifications for inserting or addition, deleting, and updates. So if something's happening in the background while your application's still running, you can get access to those changes and find out what happened. And we've got notifications for each data type because there's a bunch of different things going on here. So there's a different notification for calendars, one for tasks, and of course, one for events.
And again, you do this using the standard notification center, not the distributed notification center. Thank you. Another note on recurrence. Like I said before, a recurring event-- every occurrence of a recurring event has the same unique ID. It's the occurrence value that's important. So when you get these notifications and you get the unique IDs of the events that have changed, you need to be very careful about what it is that actually changed, because if you just work off the UID, you might pull some inaccurate data. You want to make sure you've got the occurrence right in the case of a recurring event.
So for those of you who are not familiar with responding to notifications, you declare your callback, and you always get an NS notification back. You pull out a user info dictionary. And in this case, you get an array of updated records by passing, by using that key defined in the calendar store framework, from that user info dictionary. And then you want to go ahead and look at the occurrence value.
Like I said, the UID is shared across all occurrences of a recurring event. So if you just check the start date, for example, that's also common between the events. What you really need to look at is the occurrence. And the good thing is that the occurrence is always valid. Even in non-recurring events, there is an occurrence value. And in that case, it's just equal to the start date. So whenever you get these notifications, make sure you check the occurrence value and that you're not going to make sweeping changes to a recurring event on either side. And let's have a look at some of this.
So we saw the people integration. And let's go ahead and look at the old-- This is the old Event Manager application. Open iCal here. Where are we? Here we are. So these events, very straightforward. I can go ahead and add a name and the date. Core data validation, thank you very much. So I've got all this stuff.
And this is, of course, persisted by the application. If I save that document, it's there. But it's not particularly useful. As soon as I quit out of this app, I could go do something. maybe 2 o'clock blows by. And unless I've got this app open at all times, it's not really going to be very useful to me.
this is exactly why you would want to use the Calendar Store. Because if you go ahead and save those events out and set an alarm, now the user's got something really valuable. It doesn't matter what they're doing, they're going to get that iCal alert. And if they sync their iPhone before they go outside, or if they're signed up for MobileMe, they'll just get an over-the-air sync. And now whatever they're doing, your application has reminded them that there was something important that they had to do. And that's really, if you're going to be doing personal information management or anything like that, that's really a value that your app's supposed to be providing. So hooking into the calendar store is pretty important. Let's quit out of the old app, take a look at the new one. I think we're going to look at the code first, actually.
So this My Document class is just part of the sample project. A lot of this, almost all of this code was already written. It was just basically boilerplate stuff for Core Data document support. The only thing that I really changed was-- Right here, I'm listening to two different things.
The first thing I'm doing here is this is core data speak, but I'm basically looking at the managed object context. And what this is, it's basically the controller for all the data that's being written, that's being managed by the document, both the people and the events. So basically what I'm saying here is I want to get a callback whenever an event changes, whether it's added or deleted or edited. And so that's going to allow me to hook in and see what changed in the document so I can push that right out to Calendar Store. Likewise, in the other direction, I've registered here for Calendar Store notifications, in this case, external event changes. In other words, if somebody changes something in iCal or another application using the Calendar Store framework, I can get those changes and respond to them quickly. So that's two lines of code. This is not me. This is done by the template.
And here are my callbacks. So in this case, this first method, appEventsChanged, is what happens whenever data changes inside our application, inside Event Manager. So it's a very similar pattern in terms of updates or deletes. Notice that when an event is added, we don't bother yet, because the user hasn't had a chance to edit the information. So it's not until they change the information and that the information is adequate and valid, like the app, the event needs a start date, an end date, and a name. Until all that information exists, we don't bother writing it out to iCal. And then similarly, if it was deleted, we go ahead and send that over to the calendar store. And then this is a convenience method that I wrote to get at all the events that happen to be tied to iCal events.
Now we'll go over to this file here. And you notice a little Objective-C lesson for the newbies here. The file is called CalendarStoreEditions, but the class name there is my document. And I've got parentheses there saying CalendarStoreEditions. This is called a category. And it basically allows you to add functionality to an existing class without actually creating a subclass, without messing with the hierarchy. And down here at the bottom, I've done the same thing for NSDate. So rather than subclass NSDate, which is a pretty heavyweight operation, I've just added a little convenience method to NSDate that will allow me to do some maintenance. And most of the code you see here is not actually working with the calendar store. Most of it is just the kind of paperwork involved with taking data from iCal and conforming it to how the data is stored in this application. So it's a matter of taking things like the title and finding out what the name of the property is in Core Data and setting it back and forth. Thank you.
So I don't have line numbers here, but this is about 100 lines of code to do this. And I'll walk you through it as we play around. But let's see what's going on here. I'll add a new event. You notice nothing's happened yet, because we don't have a name, we don't have a start date or anything like that. So name, contacts, and calendars.
So we've written the name out. Nothing has changed just yet. And let's see. What's today? Oh, Friday the 13th. How could I forget? 2 o'clock. Thank you. And boom. Notice that as soon as we had a valid date, and I'm doing that validation myself. I'm making sure I have the start and finish. It appeared in iCal immediately. I make these changes. I don't know. Let's say maybe you guys have a lot of questions. And a little later, you saw that it jumped down there a little bit. So these changes are instantaneous. So these are not throttled or delayed or anything like that. These things show up right away. And then the notifications are going to allow us to respond on the app side when something happens in iCal. So I go over here in iCal and I change the name.
And go ahead and watch on the left there. You see the name changed immediately. Go ahead and move this thing around. You should see it change to the start date. So it's pretty straightforward. Well, it's not straightforward, but it's pretty powerful. And all we've written is a couple lines of code. And like I said, most of this code is just wrangling the data schemes between the two data types, because one of them was defined by Apple and the other one was defined by you. Let me show you the really important stuff. Here's the callback for when I get this notification.
And like I said, when you get these notifications, you have different arrays of changes. You have addition, you have deletes, and you have just static changes to existing events. So we start by getting the updated records. And it's going to be a list of UIDs. It's not going to be a list of events.
So I go ahead and I get the local events. I mentioned this was that convenience method I declared earlier, where I go ahead and I get the dictionary of all the events that are in the application. Very straightforward. I go ahead and I compare the UID that I have against whatever happens to be on the application side, and then I call this reset from calendar store method. And this is real straightforward.
This is I have the event on my app side, and I've got an ID for something in calendar store. I just read the data out of iCal, and I just write it back to our application. In the case of deletion, I don't think I showed you that. Deletion usually takes a little while. There we go. So remove the event from iCal. We went ahead and removed it from here. Likewise, let's do another one real quick.
So there's our new one, removed, got it out. So less than 100 lines of code to do all this stuff, and we've immediately got contextual hookups with everything on the system. Because it doesn't just show up in iCal, it shows up in the address-- excuse me, the iCal widget. It shows up in the spotlight menu. And again, the user syncs that to their iPhone, and now they've got reminders of these things that presumably are important to them because they're using your application to manage them. So can we go back to slides? There it is. Address book and iCal integration in 15 minutes.
So that's it for Calendar Store. There's a lot of details that we left out in the interest of time, specifically the recurrence information. But the thing to remember is this is critical for you. If you're going to be doing any kind of event management, anything that keeps the user informed or up to date or organized, you want to make sure that that information gets carried over into all the places they might be. And if you use the Calendar Store, everything else gets taken care of for you. It gets synced through to MobileMe. It gets synced to their iPhones. and they can get little reminders right on top of their Mac even when your app's not running. That's the big win here.
So these APIs are very straightforward, and they solve, I would say, 90% to 95% of the cases out there. But there might be some situations where this straightforward API is not necessarily what you're going to need, what you're going to be looking for. You might want to have custom data extensions.
You might want to have some additional metadata attached to an event or to a person. You might want to have complete change logs. So you notice you get those notifications when you're running, and you can get fine grained, and you can go ahead and analyze the delta yourself when you get that new event. But there could be data changes when you weren't running. And if you want granularity on what exactly happened throughout the five days between your app quitting and launching again, Sync Services can give you those changes.
But the thing to remember is you need to make a choice. You can't use sync services and the address book framework, or sync services and the calendar store framework are all three, because you would get into what we call a sync loop. For example, I do a sync with sync services and I get these changes. And I mingle that into my local database. And then if I was also using calendar store, suddenly I push those changes through to calendar store. And then calendar store says to the sync database that something's changed, and then the sync database tells you that something's changed. on and on and on. So if you're going to use Sync, use Sync. But if you're going to use Calendar Store or Address Book, just use those. And like I said, most of the cases, these simple frameworks are going to do what you need.
That just about does it. When you guys go home, I like having this session at the end of the week because a lot of you are new to the platform and you need to get firmly grounded on how the Mac works and how iPhone works. Like I said in the beginning, it's frameworks like these and efforts like these that really make the difference in having a really standout, solid Mac application. You want to keep users connected. You want to add faces to names. And you want to have clean system integration. And you can get that people picker and look exactly like iChat and Mail right out of the box.
And again, keep your users organized. If you're going to be reminding them of things, let us do some of that work for you. Synchronize yourself with the Calendar Store framework. Very easy read and write access. And we'll give you free alarms. We'll give you presence all over the system in Dashboard, in Spotlight, and in iCal.
I would say that's pretty much it. For more information, you can contact me. I'm the guy whose job it is to talk to third-party developers throughout the year. and documentation you want to be interested in, go ahead and search for address book guide, calendar store guide. But also there were some things peppered in there that we talked about. So if you're new, you want to read up on things like Cocoa notifications, as well as core data and Cocoa bindings, and sync services. You don't need to understand sync if you're just going to use these frameworks. But if you get to know address book and calendar store, you decide they're not good enough for you, sync services would be the way to look. We also had a sync services session this week that you can go ahead and pull up from the videos when they become available.
So for those of you who are new, intro to Mac and iPhone development will probably teach you a lot of the things that we talked about in passing, like notifications, Objective-C 2 properties, Cocoa bindings, core data. Similarly, getting started with Objective-C. And we had an address book for iPhone session, if you're interested in accessing contact information on the phone. And again, if these frameworks are not quite doing it for you, you can go ahead and have a look at sync services. We had a great session on that earlier this week, and the video should be available any time now.