Advanced iOS Summer Bundle

3 brand-new books on SwiftUI, Combine and Catalyst — $99.99 for a limited time!

Contacts Framework Tutorial for iOS

In this Contacts Framework tutorial for iOS, learn how to select, display, save and edit contacts from the user’s device.

5/5 1 Rating

Version

  • Swift 5, iOS 12, Xcode 10

One of a mobile device’s most integral functions is storing contact information for people you want to communicate with. Whether it be through phone calls, messaging or sharing very important memes, your device would be useless for communicating without this information. You can get this information with the Contacts framework.

As an app developer, it’s important to understand how you can use contact information to better integrate your app with the user’s device. In this Contacts framework tutorial, you’ll learn how to use both the Contacts and ContactsUI frameworks.

The Contacts framework allows you to read or modify the user’s contacts from your apps. These are the same contacts that show up in the Contacts app. It replaces the old Address Book framework.

For this tutorial, you’ll be working on a modified version of an app used in a previous screen cast. You’ll be able to select, display, save and edit contacts stored on the user’s device.

Getting Started

Use the Download Materials button at the top or bottom of this tutorial to download the starter project. The app has some basic interface to show a list of default contacts. There are some buttons that you’ll be implementing actions for, so not everything works in the UI yet.

Build and run the project, and you’ll see a list of famous contacts that have been hard-coded in the app.

List of famous contacts

Before you can work with contacts, you’ll want to become familiar with CNContact, the class used to store all of the contact’s information your app has the potential to access. Open Friend.swift and look around. Friend, a class for this project, has the property contactValue.

Showing Contacts Information

If you’re working with contacts, chances are you’ll want to show this information at some point. Apple has provided a built-in view controller that can display a CNContact with very little work.

Open FriendsViewController.swift and add this import at the top of the file:

import ContactsUI

Next, add this code to tableView(_:didSelectRowAt:):

// 1
let friend = friendsList[indexPath.row]
let contact = friend.contactValue
// 2
let contactViewController = CNContactViewController(forUnknownContact: contact)
contactViewController.hidesBottomBarWhenPushed = true
contactViewController.allowsEditing = false
contactViewController.allowsActions = false
// 3
navigationController?.navigationBar.tintColor = .appBlue
navigationController?.pushViewController(contactViewController, animated: true)

Here’s what you’ve added:

  1. You got the CNContact value from the friend.
  2. You created a CNContactViewController and configured it. Here, you turned off editing and actions. If you wanted the UI to show buttons which would allow you to share the contact or your location with the person, you can set allowsActions to true.
  3. The CNContactViewController has a lighter colored theme, much the same as when you view a contact in Contacts.app. Because of the app’s theme, you need to adjust the navigation bar’s tint before presenting the contact controller, so the navigation buttons will be visible. The color is already switched back in viewWillAppear(_:).

Build and run and select any of the contacts in the list. You’ll now see the contact presented in a nice view controller without much work from you at all.

Contacts framework CNContactViewController in action

Picking Contacts

Next, you can take even more advantage of the ContactsUI framework by using the contact picker. It’s the built-in way to select a contact from the user’s device and import the data into your app.

Open FriendsViewController.swift and add the following code to the bottom of the file:

//MARK: - CNContactPickerDelegate
extension FriendsViewController: CNContactPickerDelegate {
  func contactPicker(_ picker: CNContactPickerViewController,
                     didSelect contacts: [CNContact]) {
    let newFriends = contacts.compactMap { Friend(contact: $0) }
    for friend in newFriends {
      if !friendsList.contains(friend) {
        friendsList.append(friend)
      }
    }
    tableView.reloadData()
  }
}
 

Here, you make sure the contact picked from the CNContactPickerViewController is properly added to the list, but only if they weren’t previously added.

Finally, add the following implementation to addFriends(sender:):

// 1
let contactPicker = CNContactPickerViewController()
contactPicker.delegate = self
// 2
contactPicker.predicateForEnablingContact = NSPredicate(
  format: "emailAddresses.@count > 0")
present(contactPicker, animated: true, completion: nil)

Here’s a quick breakdown of what you added:

  1. You created a contact picker and set the delegate.
  2. You set a predicate on the contact picker. You can use a predicate to filter contacts that meet a certain criteria of your choosing. In this case, you ensure that the contacts to choose from have at least one email address.

Build and run, then select the add button from the top right.

Contacts framework CNContactPickerViewController in action

If you’re running in the simulator, you’ll see a list of the default contacts with David Taylor grayed out. This is because his contact has no email address.

Select one of the other contacts from the list and then Done. You should see the contact is in the list with the default contacts.

Setting Up Contacts Permissions

Up to this point, you were able to access the user’s contacts data without asking permission to do so. You can do that because of what Apple calls out-of-process pickers. The picker runs outside your app process and the only information you get back is the data selected by the user.

For the next steps, you’ll access the contacts store directly. You’ll need the proper permissions to do that.

First, open Info.plist and select the add button next to any of the items in the property list. In the newly added item, find or type:

Privacy - Contacts Usage Description

In the value field, add the following description:

Access is needed to save your RWConnect friends information to your Contacts list.

Info.plist

The system will show this property’s value to the user when presenting the alert asking for permission.

Note: Make sure to explain the reason you need that permission properly, because vague purpose strings can lead to rejections in app review.

Next, open AppDelegate.swift and add the import for Contacts to the top of the file:

import Contacts

Finally, add the following code to application(_:didFinishLaunchingWithOptions:) right before it returns:

CNContactStore().requestAccess(for: .contacts) { (access, error) in
  print("Access: \(access)")
}

This code will trigger the system to ask for the user’s permission for access to Contacts.

Build and run the project and you’ll see the permission alert with the text value added in Info.plist. Be sure you select OK.

Contacts framework permission alert

Editing Contacts

In this final section, you’re going to edit a contact and save it back to the user’s device. Up to this point, you’ve been using the built in UI provided by the ContactsUI framework. Now, you are going to use a custom UI to edit or add a phone number to a contact and save it back to the device’s contact record.

To do this, you’re going to fetch the contact from the device and check if a phone number is already present, then display it in EditFriendTableViewController.

In FriendsViewController.swift, add the following code to prepare(for:sender:):

if segue.identifier == "EditFriendSegue",
  // 1
  let cell = sender as? FriendCell,
  let indexPath = tableView.indexPath(for: cell),
  let editViewController = segue.destination as? EditFriendTableViewController {
  let friend = friendsList[indexPath.row]
  // 2
  let store = CNContactStore()
  // 3
  let predicate = CNContact.predicateForContacts(matchingEmailAddress: friend.workEmail)
  // 4
  let keys = [CNContactPhoneNumbersKey as CNKeyDescriptor]
  // 5
  if let contacts = try? store.unifiedContacts(matching: predicate, keysToFetch: keys),
    let contact = contacts.first,
    let contactPhone = contact.phoneNumbers.first {
    // 6
    friend.storedContact = contact.mutableCopy() as? CNMutableContact
    friend.phoneNumberField = contactPhone
    friend.identifier = contact.identifier
  }
  editViewController.friend = friend
}

Here’s what you added:

  1. This gets the FriendCell selected from the table view.
  2. You create a CNContactStore. This is the class that allows you to read and write contacts through fetch and save requests.
  3. You use the class method available on CNContact to make a predicate that will filter on the friend’s work email.
  4. This creates an array of keys you want to fetch from the store. Since you are going to edit the phone number, this is the only key you’ll add.
  5. Next, you perform the fetch on the store. You can see predicate and keys passed to the fetch here.
  6. Last, if a contact matches the predicate, you create a mutable contact with the phone number added. You need to use an instance of CNMutableContact when you want to edit contact information. You’ll also notice an identifier gets added to the contact— you’ll use this later on.

Build and run. Select the info accessory, the i button, on any of the contacts and see EditFriendTableViewController now has the contact information populated.

Information populated

You may notice the phone number field is still empty. That’s because none of the default contacts have phone numbers on them – yet.

Add one of the device’s contacts to the friends list and then select the info accessory. You can see that a phone number is also populated.

In this last step, you’re going to save contact information to the device. Open EditFriendTableViewController.swift and add the following to the end of save(_:):

let store = CNContactStore()
guard 
  let friend = friend,
  let phoneNumberText = phoneTextField.text 
  else { return }
let phoneNumberValue = CNPhoneNumber(stringValue: phoneNumberText)
let saveRequest = CNSaveRequest()

Here, you created a CNContactStore and ensured that you have text that you can save to a phone number. When working with contacts, you can’t save a String type as the phone number so you need a CNPhoneNumber type.

Finally, since you’re going to be saving information, you need a CNSaveRequest. You’ll decide if a contact needs adding or updating with this request object.

Next, add the following after what you just added:

if let storedContact = friend.storedContact,
  let phoneNumberToEdit = storedContact.phoneNumbers.first(
    where: { $0 == friend.phoneNumberField }
  ),
  let index = storedContact.phoneNumbers.firstIndex(of: phoneNumberToEdit) {
  // 1
  let newPhoneNumberField = phoneNumberToEdit.settingValue(phoneNumberValue)
  storedContact.phoneNumbers.remove(at: index)
  storedContact.phoneNumbers.insert(newPhoneNumberField, at: index)
  friend.phoneNumberField = newPhoneNumberField
  // 2
  saveRequest.update(storedContact)
  friend.storedContact = nil
} else if let unsavedContact = friend.contactValue.mutableCopy() as? CNMutableContact {
  // 3
  let phoneNumberField = CNLabeledValue(label: CNLabelPhoneNumberMain,
                                        value: phoneNumberValue)
  unsavedContact.phoneNumbers = [phoneNumberField]
  friend.phoneNumberField = phoneNumberField
  // 4
  saveRequest.add(unsavedContact, toContainerWithIdentifier: nil)
}

It may look like a lot, but here’s what’s happening:

  1. In the if condition, you are checking if you are working with a contact already saved to the device. One gotcha when working with contacts is you cannot update the phone number field directly— you have to replace it. phoneNumberToEdit is the old phone number that was initially displayed, and gets updated with the new phone number edited by the user, then swapped out on the contact.
  2. Since this contact already exists and simply needs to update, you are going to pass it to update(_:) on the CNSaveRequest. This is possible because of the identifier that was set in an earlier step. If the contact doesn’t already exist, trying to save would throw an error.
  3. The else if condition is for the default contacts that don’t already exist on device. Since they don’t already have phone numbers, you need to create a new phone number from scratch. You do this using CNLabeledValue.

    You can choose from a list of different phone number labels, but in this case, you’ll save the phone number as the main phone number.

  4. Like the case of updating a contact, you need to tell the save request that you are going to add this contact to the device.

You’re almost done! The final step is to execute the save. At the end of save(_:) add the final block of code:

do {
  try store.execute(saveRequest)
  let controller = UIAlertController(title: "Success",
                                     message: nil,
                                     preferredStyle: .alert)
  controller.addAction(UIAlertAction(title: "OK", style: .default))
  present(controller, animated: true)
  setup()
} catch {
  print(error)
}

Here, you attempt to execute the save request on the store. If it works, you’ll see a success message.

Note: Methods on CNContactStore are synchronous and access the file system, so in production code you should actually run them on background threads.

Build and run and edit a default contact to have a phone number. After you save, open Contacts.app on the simulator or device and find the contact you saved. You’ll see the default information always present in the app and the phone number you added.

Contacts.app

Next, in the app, add a contact from the device. Edit the phone number and save. Back in Contacts.app, find the contact to see the phone number you updated saved.

Where to Go From Here?

You did it! In this Contacts framework tutorial, you’ve successfully learned how to use both the Contacts and ContactsUI frameworks.

You can download the completed version of this project using the Download Materials button at the top or bottom of this tutorial.

By utilizing the Contacts framework, you can simplify tasks that require contact information or enhance the contacts experience without forcing your users to maintain separate copies of their contacts. If you’d like to learn more, you can read Apple’s documentation on the Contacts framework and ContactsUI.

There’s also a great video on Apps Privacy from Apple.

If you have any questions or comments, please join the forum discussion below.

Average Rating

5/5

Add a rating for this content

1 rating

Contributors

Comments