iOS 9: An Introduction to the Contacts Framework

Introduction

With iOS 9, OS X El Capitan, and watchOS 2, Apple introduced a brand new framework, Contacts. This framework provides an object-oriented approach to working with the user's contact information and replaces the function-based Address Book framework.

In this tutorial, we are going to reimplement the basic functionality of the Contacts app on iOS so that you can learn how these new APIs work.

Prerequisites

This tutorial requires that you are running Xcode 7+ on OS X Yosemite or later. You will also need to download the starter project from GitHub.

1. Accessing Contacts

We're first going to use the Contacts framework to access the user's contacts and display them in a table view. Open the starter project and go to MasterViewController.swift.

If you scroll to the top of the file, you can see that I've already added an import statement for the Contact framework. This gives us access to the classes, protocols, and constants defined in the framework.

In the MasterViewController class, replace the empty implementation of the getContacts() method with the following. Make sure that you also add the retrieveContactsWithStore(_:) method shown below.

Let's go through this code step by step. We create a CNContactStore instance and this object is used to communicate directly with the Contacts system on iOS. We then check the authorization status of CNContactStore. If it is undetermined, we request authorization and retrieve the contacts if successful. If the application is already authorized, we immediately fetch the user's contacts.

In the retrieveContactsWithStore(_:) method, we wrap our code in a do-catch statement because two of the methods we use are throwing methods. You can read more about throwing methods and error handling on Envato Tuts+.

In the do clause, we fetch the contact groups on the device. Using the CNContact class, we create an NSPredicate object that matches all contacts within the first of the groups we just retrieved.

We then create an array that contains a number of constant keys. These keys directly relate to the information your app has access too. For any keys you don't specify (for example, phone number) your app won't be able to access that information. When working with the Contacts framework, this is referred to as a partial contact as you don't have access to all of a contact's information.

Using the store object, we retrieve the contacts matching the predicate we created earlier and with the specified keys. We assign the result to the view controller's objects array to be displayed in the table view. We force the table view to reload on the main thread. This is important because fetching the contacts is performed on a background thread.

There are a few key things to take note of:

  • The descriptorForRequiredKeysForStyle(_:) class method used on CNContactFormatter is a convenient way to easily add all of the keys needed to view a contact's name.
  • The predicate you create doesn't have to be for a contacts group. It can be one of many things, including searching for a matching name for example.
  • In the Contacts framework, there is no way for your app to directly access every single contact. That is why we use the above code to retrieve the first contacts group rather than all the contacts.
  • When retrieving the contacts, we use the unifiedContactsMatchingPredicate(_:keysToFetch:) method. What does "unified" mean in this context? If a user has multiple contacts that relate to the same person, they can link these together in the Contacts app. When your app tries to access this user's contacts, rather than returning multiple CNContact instances, the Contacts framework unifies these together into one object so your app can display information correctly and interpret it easily.

Next, we need to display the contact's information in the table view. In the MasterViewController class, replace the implementation of tableView(_:cellForRowAtIndexPath:) with the following:

We make use of the CNContactFormatter class to easily fetch a String value of the contact's name. We also get the first email address for the contact (represented by a CNLabeledValue object) and get its value. A CNLabeledValue object represents any contact information where a piece of contextual information may be needed. These objects contain just one label and one value. In the following example, words in bold represent an item's label and the words in italic represent their value.

  • Home address
  • Work phone
  • Personal email

Build and run your app in the simulator. When you run the app for the first time, you should see the following alert.

Contacts authorization alert

After clicking OK, the table view should display one item as shown below.

Populated table view

It's now time to fill in the detail view when a contact is selected from the table view. When retrieving the contacts, we only fetched enough keys to access a contact's name and email addresses. For the detail view of our app, we also want to display their address as well as a profile picture. To do so, we could add the extra keys in the MasterViewController class when retrieving the contacts. However, we are going to retrieve the same contact again with the keys we need by using the identifier of the contact.

Open DetailViewController.swift and replace the implementation of configureView() with the following.

Let's break the implementation down. We get an unwrapped reference to the contact item received from the MasterViewController and we create another CNContactStore instance.

In a do-catch statement, we create another keysToFetch array, this time with the keys for postal addresses, image data, and image data available. We then use the contact store to retrieve a new CNContact instance with the information we need by using the unifiedContactWithIdentifier(_:keysToFetch:) method.

Again, note that we update the user interface on the main thread. We check to see whether or not the contact has image data available to load and turn this into a UIImage object if possible. We populate the fullName and email labels with the correct information.

While not strictly necessary in this example since we know which keys are available, we check to see whether or not our app can access the contact's postal addresses information. Note that this step is just an example but should always be done with contacts if you are unsure about which information you can access.

We retrieve the contact's first postal address (represented by the CNPostalAddress class) and format this into a string using a CNPostalAddressFormatter instance. The CNPostalAddress class functions similarly to the CNContact class but it has different properties, such as street, province, and country.

Build and run your app in the simulator and select a contact from the list. The detail view that appears should look something like this:

Contact details view

2. Creating and Updating Contacts

In addition to retrieving contacts, you can also create and update existing contacts using the CNMutableContact and CNSaveRequest classes. Open AddContactViewController.swift and replace the contact property with the following implementation:

We create a CNMutableContact object and assign a givenName and familyName to it. Note that we use the ?? or nil coalescing operator. If the value on the left of the ?? operator is nil, the value on the right is assigned instead.

We create a CNPhoneNumber object to represent the mobile number entered into the text field. We then put this number into a CNLabeledValue object with the constant CNLabelPhoneNumberMobile label. The use of the CNPhoneNumber class is required because phone numbers can be formatted in many different ways in a variety of regions. This class takes a string and creates a phone number value which the rest of the Contacts framework can then work with. The mobileValue is then put into an array and assigned to the mutable contact's phoneNumbers property.

We create a similar CNLabeledValue for the email, giving it the CNLabelHome label. This value is then assigned to the contact's emailAddresses property. We check to see if an image has been assigned to the contact and, if so, assign its raw data to the contact's imageData property.

To save the contact, we create a CNSaveRequest object and call its addContact(_:toContainerWithIdentifier:) method to tell the Contacts framework that we want to create a new contact. By passing nil as the identifier, the new contact will be saved in the default contacts group.

In another do-catch statement, we tell the contact store to execute the save request. Lastly, we return the newly created contact for use in the rest of the app.

Build and run your app and click the plus button in the top right corner to add a new contact. Fill out the form, add a photo, and click Done. Your new contact should then be added to the table view of the master view controller as shown below.

Creating a new contact
New contact in table view

Updating an existing contact is very similar to creating a new one. While we won't be implementing this in the app, the code for updating an existing contact would be similar to the following:

3. Contact Picker View Controller

As I mentioned in the first part of this tutorial, the Contacts framework doesn't have an API for directly accessing every contact on the user's device. This is to protect the user's privacy so that apps cannot read all of their contacts and collect information.

Luckily, the framework provides a UIViewController subclass, CNContactPickerViewController, that gives the user access to all the contacts stored on the device.

Revisit MasterViewController.swift and add an import statement at the top for ContactUI.

Next, make the MasterViewController class conform to the CNContactPickerDelegate protocol.

Replace the implementation of the addExistingContact() method of the MasterViewController class with the following:

Lastly, add the following method of the CNContactPickerDelegate protocol to the MasterViewController class:

Build and run your app one last time and click Add Existing in the top left corner. A view controller similar to the following should appear:

Contact picker

If a contact in the contact picker view controller is selected, the view controller dismisses and the selected contact is added to the table view of the master view controller.

The contact picker view controller also supports multiple selections, depending on the methods that the delegate implements. It can also be customized to access specific properties as well as to filter the contacts it shows based on predicates. For more information, I recommend reading the CNContactPickerViewController class reference and the CNContactPickerDelegate protocol reference.

Conclusion

As you can see, the new Contacts framework in iOS 9, OS X El Capitan, and watchOS 2 is a very well designed and easy to use collection of APIs. You should now be comfortable accessing, creating, and updating contacts on a user's device. As always, be sure to leave your comments and feedback in the comments below.

Tags:

Comments

Related Articles