Picking Contacts with the Prebuilt System UI - Contacts - iOS 9 Swift Programming Cookbook (2015)

iOS 9 Swift Programming Cookbook (2015)

Chapter 4. Contacts

4.6 Picking Contacts with the Prebuilt System UI

Problem

You want to use a built-in system dialog to allow your users to pick contacts from their contact store.

Solution

Use an instance of the CNContactPickerViewController class inside the ContactsUI framework.

NOTE

Instances of the CNContactPickerViewController cannot be pushed to the stack. They need to be presented modally. Use the presentViewController(_:animated:completion:) method of your view or navigation controller to display the contact picker modally.

Discussion

Let’s say that you want to allow the user to pick a contact. You will then attempt to read the phone numbers from that contact. Instances of the CNContactPickerViewController class have a property called delegate that is of type CNContactPickerDelegate. A ref of the interesting methods in this delegate include:

contactPickerDidCancel(_:)

This gets called when the user cancels their request to pick a contact

contactPicker(_:didSelectContact:)

This gets called when the user picks a contact from the list

In this example, I want to allow the user to pick a contact, whereupon I will read all the phone numbers from that contact. I have placed a button in my storyboard and hooked that button to a method in my code called pickaContact(). In that code, I present a simple contact picker:

let controller = CNContactPickerViewController()

controller.delegate = self

navigationController?.presentViewController(controller,

animated: true, completion: nil)

NOTE

I’m doing all this code inside a view controller and I’ve made my view controller conform to CNContactPickerDelegate.

Then, when the user picks a contact, I just print out all the phone numbers of that contact, if any, to the console:

func contactPickerDidCancel(picker: CNContactPickerViewController) {

print("Cancelled picking a contact")

}

func contactPicker(picker: CNContactPickerViewController,

didSelectContact contact: CNContact) {

print("Selected a contact")

if contact.isKeyAvailable(CNContactPhoneNumbersKey){

//this is an extension I've written on CNContact

contact.printPhoneNumbers()

} else {

/*

TOOD: partially fetched, use what you've learnt in this chapter to

fetch the rest of this contact

*/

print("No phone numbers are available")

}

}

NOTE

The printPhoneNumbers() function is a custom extension on CNContact that I’ve written. You don’t have to know the implementation of that as it’s not relevant to this recipe. You already know how to do that using what you learned in Recipe 4.2.

In this example, we are looking for contacts with phone numbers, but the user is allowed to pick any contact, even if that contact has no phone numbers. How do we remedy this? A property called predicateForEnablingContact of type NSPredicate, on instances ofCNContactPickerViewController, allows us to specify which contacts should be enabled and which ones should be disabled. Here we can create a predicate that checks the @count of the phoneNumbers property. Also, for fun, let’s say that we want to only allow contacts whose names starts with “John” to be selectable (see Figure 4-7).

let controller = CNContactPickerViewController()

controller.delegate = self

controller.predicateForEnablingContact =

NSPredicate(format:

"phoneNumbers.@count > 0 && givenName BEGINSWITH 'John'",

argumentArray: nil)

navigationController?.presentViewController(controller,

animated: true, completion: nil)

Figure 4-7. Only people whose names starts with “John” and who have at least one phone number are retrieved

The predicateForEnablingContact property disables all contacts who do not pass the predicate so that the user won’t even be able to select those contacts. There is another property on CNContactPickerViewController that does something more interesting:predicateForSelectionOfContact. The contacts that pass this predicate will be selectable by the user so that when the user taps on that contact, the controller is dismissed and we get access to the contact object. The contacts that do not pass this predicate will still be selectable, but opon selection, their details will be shown to the user using the system UI. They won’t be returned to our app.

let controller = CNContactPickerViewController()

controller.delegate = self

controller.predicateForSelectionOfContact =

NSPredicate(format:

"phoneNumbers.@count > 0",

argumentArray: nil)

navigationController?.presentViewController(controller,

animated: true, completion: nil)

There is another funky property on CNContactPickerViewController named predicateForSelectionOfProperty. This is a predicate that dictates which property for any contact the user should be able to pick. If you want to allow the user to pick a specific property--say the first phone number--of any contact to be passed to your app, you also have to implement the contactPicker(_:didSelectContactProperty:) method of the CNContactPickerDelegate protocol. Let’s write sample code that allows the user to pick any contact as long as that contact has at least one phone number, and then be able to pick the first phone number of that contact to be returned to our app:

let controller = CNContactPickerViewController()

controller.delegate = self

controller.predicateForEnablingContact =

NSPredicate(format:

"phoneNumbers.@count > 0",

argumentArray: nil)

controller.predicateForSelectionOfProperty =

NSPredicate(format: "key == 'phoneNumbers'", argumentArray: nil)

navigationController?.presentViewController(controller,

animated: true, completion: nil)

And then we provide an implementation of the contactPicker(_:didSelectContactProperty:) method:

func contactPicker(picker: CNContactPickerViewController,

didSelectContactProperty contactProperty: CNContactProperty) {

print("Selected a property")

}

In addition to all of this, you can also allow the user to pick multiple contacts. Do that by implementing the contactPicker(_:didSelectContacts:) method of the CNContactPickerDelegate protocol (see Figure 4-8).

func contactPicker(picker: CNContactPickerViewController,

didSelectContacts contacts: [CNContact]) {

print("Selected \(contacts.count) contacts")

}

//allows multiple selection mixed with contactPicker:didSelectContacts:

func example5(){

let controller = CNContactPickerViewController()

controller.delegate = self

navigationController?.presentViewController(controller,

animated: true, completion: nil)

}

Figure 4-8. The user is able to select multiple contacts at the same time and return to our app at the end

See Also