Introducing the Contacts Framework for iOS and OS X

Session 223 WWDC 2015

Whether it's getting contact information, managing a social graph, or adding a new friend, Contacts are a critical piece of many apps. OS X El Capitan and iOS 9 now share a new Contacts API. Understand the design, goals, and benefits of this new framework. Gain critical insights into how to make a smooth transition for your app to this new framework.

[ Applause ]

BRUCE STADNYK: Good afternoon!

I am Bruce Stadnyk from the iOS contacts team and I'm excited to introduce the new Contacts Framework to you.

[ Applause ]

BRUCE STADNYK: If you are new to Apple's platforms you will learn how easy it is to use contacts in your app.

If you are an experienced developer with Address Book, you will love this new Framework.

So what is this new Contacts Framework?

Well, we have been listening to your Address Book feedback and today we are addressing the most frequent request.

The Address Book Framework provides an Objective-C API to access contacts, and the API is designed to work well with Swift [ Applause ]

BRUCE STADNYK: We are as excited about this as you are.

There are many design goals with this new Contacts Framework.

I'm going to review a few of the key ones with you now.

One, to address the majority of apps that are mostly get contacts and do not modify contacts, we have designed the API for thread safe read-only usage.

This is done mostly with immutable value objects that do not reference a data store.

This allows you to pass contacts easily between queues on your app and not have unexpected I/O occur on them.

Another is to have the same contacts API on OS X, iOS and watchOS.

[ Applause ]

BRUCE STADNYK: You learn one API and then use the same code to access contacts across multiple Apple platforms.

And, Address Book, if you are using it, it is being deprecated.

[ Cheers and applause ]

BRUCE STADNYK: We don't realize how much we take for granted the contacts on our devices.

For example when we receive a phone call and we only see a phone number, we ask ourselves: Who is that?

Is it someone from our family?

Is it a friend?

Another telemarketer?

It is a much better experience when a contact is displayed.

Oh, it's John Apleseed, one of my best friends.

So everyone has contacts on their devices.

They help us to identify who we are communicating with and also help us to make phone calls, send e-mails, and initiate other means of communication.

For example, I can speak to my device and say: Hey, Siri, call John Appleseed.

Okay. There we go.

SIRI: Calling John Appleseed, iPhone on speaker.

BRUCE STADNYK: So contacts are a central part of the user experience on our devices.

So how does the Contacts Framework handle this contact information?

Let's take a look at an example with John Appleseed.

For those of you experienced with Address Book, this is a quick review.

Here we have John's profile picture, his first name, last name, his home e-mail address, work e-mail address, and the phone number for his iPhone.

These contact properties represent this contact information.

The profile image is represented by image data.

The name is broken into components.

So the first name is represented by given name and the last name by family name.

Both the home and work e-mail addresses are represented by e-mail addresses.

And the phone number is represented by phone numbers.

There are many more contact properties.

You can refer to the contacts documentation for more details.

Now, let's talk more about this object.

This is the CNContact object.

It is an immutable value object of contact properties.

It is modeled like NSDictionary.

It has a mutable subclass, CNMuteableContact, that you modify the contact properties with.

You will see this pattern throughout the Contacts Framework.

For contact properties that can have multiple values like e-mail addresses, or phone numbers, an array of CNLabeledValue is used.

CNLabeledValue is an immutable Tuple of label and value, where the label is a string, and the value is an object.

Like string for an e-mail address.

The value can be labeled to help differentiate it from the multiple values for a property, such as a home e-mail address, or a work e-mail address.

For those of you experienced with Address Book, AB multivalue is replaced by this array of CNLabeledValue.

Yes, you heard that correctly, AB multivalue is dead.

[ Applause ]

BRUCE STADNYK: Now I would like to take a look at an example of creating a new contact with John Appleseed.

So first we import Contacts, then we create a mutable contact, as we are going to be adding to it.

We then set John's profile picture as NSData to imageData, and then we set his name to givenName and familyName.

Now, for John's two e-mail addresses, we create two CNLabeled values, one with CNLabelHome, the other with CNLabelWork.

These are some of the predefined labels in the Contacts Framework.

These predefined labels have localized strings that you can use in your apps UI.

You can also create your own custom labels.

We take these two label values, home e-mail and work e-mail, place them in an array, and set them on e-mail addresses.

It's that simple.

And we can do the same thing with John's phone number.

Again, we create a CNLabeled value, use the iPhone predefined label, create a CNPhoneNumber object as the value, place the label value in array, and set that to phone numbers.

I also know John's home address, so I can add that as well.

I create a CNMuteablePostalAddress, set the appropriate information, again create a labeled value using LabelHome, place that in array, and set that to postal addresses.

Finally, I also know his birthday, so I can create an NSDateComponent, set the day, month, and year component, and set that to the birthday property.

Note that all date-related properties in the Contacts Framework are NSDate components.

This allows flexibility to have dates such as year-less birthdays, where you would omit the year component.

The Contacts Framework is also able to perform operations on these contact objects.

Of interest here is formatting contact data.

The CNContactFormatter will format a contact's name.

In this example, we're formatting the full name, and get back John Appleseed.

CNContact Formatter will correctly format international names.

We also have a formatter, CNPostalAddressFormatter for formatting mailing labels for postal addresses.

That returns back...that.

CNPostalAddressFormatter will correctly format international postal addresses.

We recommend that you use these Formatters in your app when appropriate.

Now, I would like to invite Dave up to the stage to show you how you can use Contacts in your app.

[ Applause ]

DAVE DRIBIN: Thank you, Bruce.

My name is Dave Dribin, and I work on the OS X Contacts team.

Sorry. So Bruce just showed you how to create and modify CNContact objects in code.

However, these already has many contacts as part of the Contacts app of OS X, iOS and watchOS.

And Bruce also showed how system apps can integrate with these contacts to provide a richer user experience.

For example, the phone app can show the person's name and photo instead of just a phone number, for an incoming phone call.

The Contacts Framework allows you to provide that same rich experience for your app.

So the class you are going to use to access a user's contacts is called CNContactStore.

We are going to talk about how to fetch and save users' contacts.

Let's start off with fetching.

The main method you are going to use is called unifiedContactsMatchingPredicate, keysToFetch.

This is going to return an array of CNContact objects.

The predicate in Keys to Fetch are there in order to help your app fetch as efficiently as possible.

Let's start off with the predicate.

The user might have hundreds, or even thousands of contacts.

You might only be interested in a small subset of those.

The predicate allows you to, ah, helps you limit the number of results returned.

Now, for those that don't know, NSPredicate is a standard foundation-level object that presents criteria to be matched against an object while searching.

The Contacts Framework provides predicates you can use with the contact store.

And the contacts store will evaluate these against the predicate, or the contact.

The example here is predicate ForContactsMatchingName.

And this will match each of the contacts against the specified name, in this case Appleseed.

Let's see a quick example.

Say the user has these three contacts.

John Appleseed, Jane Appleseed, and Craig Bromley.

The contacts store can effectively evaluate this predicate [John Appleseed] against each of the contacts [Jane Appleseed; Craig Bromley] and only return the matching ones.

In this case John and Jane are going to be returned, but not Craig.

So the predicate allows you to limit the number of contacts returned, but there's still a lot of contact information on a contact, and you might only be interested in a small subset of that, and that's where keysToFetch comes in.

KeysToFetch is an array of strings.

And these are strings of keys, in the key-value-coding sense.

So if you are just interested in the given name and family name, you could set up your keysToFetch like this.

Of course, using string literals is error prone.

We provide constants for this.

And what the Contact Store is going to do is it's only going to fetch the properties that you specify.

In this case, given name and family name.

So you can really see how the predicate in keysToFetch allows you to scope down the amount of information to returned so that your app can be as efficient as possible.

Let's go over a full example.

So here we are going to start off with a predicate and keysToFetch as we had earlier, but now you need a contacts store to use these on.

And this is easy, you can just create one with the default initializer.

Then you call unified ContactsMatchingPredicate, keysToFetch with those values, and assuming this works, you're going to get back an array of contacts, and then you can use them however you want.

Here we are just going to print out the given name and family name.

There's a couple of important things I want to talk about, about fetching.

The first one is that the lifetime of a CNContact is not tied to the lifetime of a CNContact store.

There is no need to keep a strong reference to the Store around after the fetch completes.

It means the data on the CNContact is, ah, valid and it is from the time of the fetch, basically a snapshot from the time of the fetch, and it is valid for the lifetime of that CNContact.

And the second important thing, is that this is a synchronous method, and fetching contacts is a relatively slow operation.

As such, you should really fetch these on a background queue, to keep your user interface responsive.

And as Bruce mentioned earlier, CNContacts are completely thread safe, so it's safe to take these from the background queue, and move them over to the main queue to update your user interface.

And another really good reason to fetch on a background queue is data privacy.

So, users take the privacy of their contacts very seriously.

And as such, we want to put up a barrier between your app, and the users contacts.

The very first time your app accesses contacts through the API, the OS is going to show a dialogue box or alert asking the user to allow or deny access.

You have probably seen these before.

This means that the first time you call a method on the contacts store, it cannot provide the results until the user responds, and that can be a long time.

So while you can move your contacts store access off to a background queue using GCD or NS operation, we provide a helper method here, an async method called request access for entity type completion handler.

Now the user may deny access, and your app should be able to handle this gracefully.

And if the user allows access, be sure to handle this contact data with care.

Please see the privacy in your app session for more information.

I would like to talk a little bit more about keysToFetch and how they return partial contacts.

So as I showed previously, the keysToFetch allows you to only fetch the properties you are interested in.

In this case the given name and family name.

What if you tried to access a property you didn't request for example a phone number?

It's going to throw an exception, because that data is not there, and we call these partial contacts because only some of the properties are available.

Now normally this isn't a problem if you set up your keysToFetch properly,as we did in the previous examples, but there are times you are going to get a contact, and you are unsure which keysToFetch was used when it was fetched.

In those cases, you are going to want to check to see if the key is available before accessing the property, just as you would check the length of an array before indexing into it, to avoid an exception.

Here this example is using the isKeyAvailable method to see if the PhoneNumbers key is available for accessing the phone numbers property.

You might be thinking "well, that's great, but I really wanted to access those phone numbers."

In those cases, you can re-fetch the contact with the additional keysToFetch.

Let's see how that would work.

So, here we are setting up keysToFetch, but this time we're using the phone numbers key, and using a method called unifiedContactWithIdentifier.

Now each contact has a identifier that uniquely identifies it, and you can use to re-fetch at a later point in time.

And then, once you re-fetch, you can safely access the phone numbers on this re-fetched contact.

In partial contacts it's important to understand how they work with the rest of the framework.

Now, the previous example showed how we fetched the given name and family name and then printed them out, to print the full name, but that's really not ideal.

We should really use one of the formatters, sorry, the CNContact formatter that Bruce showed earlier, and the formatter may access other properties that you haven't fetched, like name, prefix, or suffix.

If they're not there, it's going to throw an exception.

So, we could document all the keys you need in order to use the formatter, but that's rather tedious and error prone.

So we've come up with the concept of key descriptors.

And key descriptors represent a set of keys for a particular operation.

In this case, the formatter knows which keys it needs to do its job, so it is providing the key descriptors with the descriptor ForRequiredKeysForStyle method, and you can include this directly in your keys to fetch.

And this tells the contact store all of the properties that it needs to fetch for the formatter.

Let's see an example of this.

So in this example, we want to fetch all contacts with the name Appleseed, and we want to print their full name and e-mail addresses.

We are going to set up the predicate as we did before, and set up the keysToFetch a little bit differently.

We are going to start off with descriptor for required keys for style (.FullName).

And this is going to, uh, allows us to get the full name, using the Contact Formatter later on.

You can also include the CNContact e-mail addresses key, because we want to print the e-mail addresses.

You can intermix key descriptors and CNContact keys in the same array.

With this setup, you fetch, just as we did before, calling unified contacts matching predicate, and then with the results, you can get the full name with the formatter and the e-mail addresses.

So one other important point about fetching is unified contacts.

You may have the same contact in multiple accounts.

For example, you might have a John Appleseed in your iCloud account with his work e-mail address and phone number, but you might also be friends with John on Facebook.

And that is going to have an image, home e-mail address, and birthday.

So rather than display two separate contacts there, the contacts app is going to use some heuristics to link these together, and display a single contact with the union of the information, and we call these unified contacts.

And contacts apps have been doing this for a few releases now.

The Contacts Framework will return unified contacts by default, as you may have guessed from the method names.

So the good news is, that means that you're going to get the data back that the user sees in the app.

The really great thing here is that these are ordinary CNContact objects, and they work and act just like any other CNContact object.

you can even modify them and save them and it will just work.

So speaking of saving, let's go over a few code examples on how to save contacts.

I'm going to start off by adding a new contact.

So let's say you've got a C immutable contact, and you set it up with data, as Bruce showed you earlier.

In order to get this into the user's contacts, you are going to use what's called a save request, and you are going to execute that save request on a contact store.

The first thing you do is create a new CNSaveRequest object, and then you are going to call add contact, to container with identifier.

Now we don't have time to talk about containers, but the nil container identifier means the default container, and, so please refer to the documentation for more information.

And save request just marks a contact for being added.

It doesn't actually apply this change yet.

For that you need to call execute SaveRequest on a contacts store.

Assuming this completes successfully, this will be added to the user's contacts.

Updating an existing contact is very similar, but now you are going to be starting off with, say, an immutable contact that you got from a fetch.

So, the first thing you are going to want to do is create a mutable copy, and then make any changes you want.

For example, here we are adding in a new e-mail address.

Now, it is important to note that, when you make a mutable copy of a partial contact, only modify the property that you had fetched.

And just as before, we need to use a save request.

Create a new save request, but this time we are going to use the updateContact method.

And again, this just marks the contact for being updated, and doesn't actually apply those changes until you call executeSaveRequest.

And, a couple of important things about saving.

Number one is that the save request can include multiple changes, and when you execute it, all of them will be applied during, um, when it gets executed.

And the second is that the save request requires mutable contacts, so you want to be careful not to access these mutable contacts on another thread, while a save is in process.

So now that you know how to fetch and save a user's contacts, I'll invite Julien up on stage to talk about using contacts in the user interface.

[ Applause ]

JULIEN ROBERT: Thank you, Dave.

Good afternoon.

I'm Julien and I'm an engineer in the iOS contacts team.

And now that Bruce and Dave made you experts in the Contacts Framework, I will talk about user interface and show you how to pick and display contacts in your app.

Coming along with Contacts Framework we have a new UI Framework called contacts UI.

It is available on both iOS 9 and OS X El Capitan.

This provides you with two clusters, first the picker, which displays a list of all the users contacts, to let him selectively import contact information into your app.

And secondly, the contact view controller, which is used to display a contact.

In the rest of this presentation I will talk about the iOS version of the classes, but the OS X counterparts are pretty similar.

Let's talk about picking contact first.

The class you use for that is CNContact Picker View Controller.

It is a direct replacement of the class we had in Address Book UI, AB People Picker Navigation Controller.

As you can guess by its name, it is a direct subclass of UA View Controller but you still must present it, and not push it in a navigation controller.

It is always out of process.

It has the great advantage of not requiring the user to let your app access its contacts.

So when you present a contact picker, you will never see the dialogue that Bruce that Dave showed.

One thing that is important is that the contact picker may return partial contacts.

For example, if you set a limited set of displayed property keys, you will only get those keys in the contact that you get back.

And the behavior of the picker is defined by two things, first the delegate method that you implement, and second, the predicate that you set on it.

And we are going to talk about those just after.

But last but not least the picker must support multiselection.

[ Applause ]

JULIEN ROBERT: Let's talk about the delegate method first.

So if your app is interested by a single contact, and you want have this familiar look of the picker, you simply need to implement the didSelectContact delegate method, and you get a CNContact back.

So again, these contacts may be partial.

If you are interested in a single property, you implement a did Select Contact Property delegate method, and you get a CNContactProperty object.

So, this object is actually from the Contacts Framework but we haven't seen it before, so let's take a look.

It is a simple wrapper class that contains the contact that was selected, as well as the key of the property that was selected by the user.

Its value and potentially, the identifier, is the property, is labeled value.

But you may also be interested in having multiple contacts.

And you would get this appearance.

To do that is very simple.

You just implement the didSelectContact delegate method, and you get as expected, an array of sent contact objects.

This also works for properties, you can get multiple of them by implementing the didSelectContactProperties delegate method, and get an array of CNContact properties.

All right.

So now let's talk about predicates.

Predicates lets you customize the behavior of the panel and we have three of them.

The first one is predicate for enabling contact.

This lets you decide which contacts are available to the user and which are not.

If I take the example that we've seen earlier, and if you want a user to only select members of the Parker family, for example, you would create a predicate where you want to match contacts with the Parker family name.

You set it as a predicateForEnablingContact.

And once the picker is represented, you can see that you can only select people with the Parker family name.

The second one that we have is predicate ForSelectionOfContact.

This one is evaluated when the user will tap on the contact.

And if it evaluates to true, then the contact is returned to your app.

Otherwise we will display the contact card.

Similarly for properties we have predicate ForSelectionOf Property.

And if it evaluates to true, the property that is tapped by the user will be returned to your app, otherwise the default action will be performed, such as making a phone call or creating a new e-mail.

One thing to note is that this last predicate is evaluated on [the] CNContactProperty object, whereas the first two are evaluated on CNContact objects.

I would like to point out that you need to be coherent between the predicates you set and the delegate methods that you implement.

For example, you should only implement the didSelectContact delegate method, but set the predicate for selection of property predicate, then, doesn't really make sense, and you would see a log, and your predicate will be ignored.

Right, so now that you know how to pick contacts, let's talk about viewing them in your app.

So we now have just one class to replace the three classes what we had in Address Book UI.

But you can still get the behavior that you want by using the appropriate creation method.

The first one is viewControllerForContact, which would give you a view controller that is the same as in contacts app, iPhone app.

If you want to create a new contact, you can use viewControllerForNewContact, and you get this View Controller which is always in editing mode.

And lastly, if you have a contact that is from an unknown origin, such as a vCard, for example, you use viewControllerForUnknownContact.

One thing we've added in iOS 9 is this update contact button which will be displayed automatically if there is already a contact in the user's contact matching the name of the contact you are displaying.

And if the user taps on it, it will display the UI to update the existing contact with the new information.

This Contact View Controller is now always out of process, and the reason for that is that we may add some additional information such as contacts data that was found in mail.

And as Dave pointed out, it is important to fetch the contacts using the right keys.

So if you want to display it in the Contacts View Controller, it must be fetched using the descriptorForRequiredKeys of the Contacts View Controller.

So let's look at a quick code example on how to display a contact from an identifier.

First you would fetch the contact with the descriptorForRequiredKeys of the Contact View Controller.

Then you create the Contact View Controller with forContact, because we want a regular look at the Contact View Controller.

If you already have a contact store, you should set it to the Contacts View Controller so that we can reuse it.

Set self as a delegate.

Push the View Controller.

And then in your delegate method you will get called when the contact is modified.

Alright, so it is nice to look at a code sample, but what is better is [to] build an app together, let's do that now.

And the app we are going to build is called Meow.

And it's an app that lets you share your emotions with your friends by sending them cat sounds.

So if I run the app, you can see at the top is a mood selector.

And then at the bottom is your friends list, which is empty for now, and this is what we are going to build now.

The app is using an e-mail address to send the cat sounds.

So what we need is just a nickname and e-mail address.

So if I hit save, nothing happens because it is not implemented yet.

So this is what we are going to do.

When I hit save, this addBuddy function is called, and we will implement it now.

First we create a mutable contact because we are going to modify it.

Then we set the nickname to be the value from the text field.

The emailAddresses property is set as an array of a single label value.

We have the value being from the text field.

And finally we add this new contact to our list of contacts.

So let's run and see how it works now.

So I'm going to use prefilled values, and add Emily to my friends, OK, my family.

You can notice this info button here.

And we want to show the contact view when we click on this info button.

So this is what we are going to implement now.

So when I tap this button, the showContact function is called.

So let's fill it in.

First we create a Contact View Controller for the contact that was, ah, that we want to show.

Then we set the contacts store to the one we already have.

And finally, we push the View Controller.

Let's just try, and okay, we can see the info for Emily.

Great. But what if we want to add a contact in our list, that is already in the user's contacts?

So this is what we are going to do here.

And for that we are going to use the contact picker.

So this function is called when I tap on addFromContacts.

And I am going to implement it now.

First thing I'm doing is create the contact picker controller.

Then since we are only interested in e-mail addresses, I limit the properties to e-mail addresses.

Then we want, ah, to, the user to select contacts who have only at least one e-mail address.

We see this is the part of the predicate that is doing that.

And who are not already in our list of friends, because we don't want duplicates.

Finally, is the user, is contact already, [who] has exactly one e-mail address, we don't need to push the [card?], We want it to be written directly.

This is what this predicate is doing.

We set ourself as a delegate and we present the View Controller.

We also need delegate methods where we add the new contact to our list of contacts.

So it is straight now.

And if I select John Appleseed, I know he has two e-mails.

So the contact card is pushed.

I would select one e-mail, and we have John in our list.

If I add a new contact, first you can see that now John Appleseed is unavailable because he is already in my friends list.

But if I select David, he just has one e-mail address.

So the contact is returned directly.

So what happens if I want to show the card for John?

So we have an exception.

And the reason is that because we were not careful when presenting the contact here, because the contact picker returned the partial contacts, it is missing some keys for the Contact View Controller.

So we are going to fix that by first checking if the contact has the required keys to be used in the Contact View Controller.

And if he does, we just use the code that we had before.

But if he does not, we will first ask the Store to request access for contacts.

If the user grants access, we are going to have to re-fetch the contact using its identifier.

And this time using the keysToFetch, the descriptor for recorded keys of the CNContactViewController.

Then we call the same function again but this time with our complete contacts ready to be used.

So let's try again.

So if I view the contact card for Emily, we have no dialogue showing up because Emily was created in code.

So she has all the fetch keys.

If I do the same on John, I re-fetch is necessary.

So at this point your app will access the user's contact.

So we say okay, and we can see all the contact information for John.

All right.

So this concludes our demo.

Which makes me a bit sad.

So I will share my emotions with John.

(Sound of cat crying.) [ Applause ]

JULIEN ROBERT: And I will call Bruce back to conclude this presentation.

BRUCE STADNYK: Thank you, Dave and Julien for showing us how to use contacts in our apps.

So you now have an Objective-C API to access contacts, and it works well with Swift.

The Contacts API is the same across multiple Apple platforms.

Address book is being deprecated, so start adopting now.

For more information you can refer to the contacts in ContactsUI Framework References on the developer library.

For technical support you can go to the Developer Forums, and for general inquiries, Paul loves e-mail, so you can e-mail him, and he's our app Frameworks evangelist.

Thank you [ Applause ]

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US