Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2008-556
$eventId
ID of event: wwdc2008
$eventContentId
ID of session without event part: 556
$eventShortId
Shortened ID of event: wwdc08
$year
Year of session: 2008
$extension
Extension of original filename: m4v
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2008] [Session 556] Accessing C...

WWDC08 • Session 556

Accessing Contacts and Calendars on Leopard

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

SD Video (205.6 MB)

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

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 writing an application that does contact management or project management and has either a trivial or 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 A/B 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 A/B 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. You 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 AB Multivalue.

These are basically typed collections. You can think of it as a dictionary if you want. But the keyword is typed. So we have string-based multivalues for things like phone numbers, IM, handles, email addresses, etc. 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 multivalue 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 multi-string, multi-date, multi-dictionary, etc. So the first thing you need to do is find out what data you're going to 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 Multi-Value. 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, and 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 going to 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. You know, the standard matches, standard compare types that you would expect in a search like this.

So that's searching. 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 the brute force approach. I wanted to get all the emails and all the phone numbers and all the aim properties, all the aim names out of everybody the address 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, but Hi, I'm Matt Drance, and I'm the founder of the Leopard platform. I'm a member of the Leopard team, and I'm going to talk about the Leopard platform.

I'm going to talk about the Leopard platform, and I'm going to talk about how to use it. 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 image 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 that much -- 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 have already written something and are just looking at Address Book and Calendar Stores and Afterthought. 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 somebody that doesn't match the Address Book, you should use that default NS user image. 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 additions later.

But what I've done is I've created this special subclass of NSManageObject. 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.

Hi, I'm Matt Drance, and I'm here to talk about the CASE-insensitive search. I'm a CASE-insensitive searcher, and I'm going to talk about the 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 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 NSImageNamedNsUser. This is literally the code 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. Maybe it's also email, 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 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

[Transcript missing]

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