Searching for Contacts - Contacts - iOS 9 Swift Programming Cookbook (2015)

iOS 9 Swift Programming Cookbook (2015)

Chapter 4. Contacts

4.2 Searching for Contacts

Problem

You want to search the contacts available on a device.

Solution

There are various ways of fetching contacts from a store. Here are some of them, in no particular order:

unifiedContactsMatchingPredicate(_:keysToFetch:) method of CNContactStore

This allows you to fetch all contacts that match a certain predicate.

enumerateContactsWithFetchRequest(_:usingBlock:) method of CNContactStore

This allows you to enumerate through all contacts that match a fetch request. The fetch request can have a predicate if you want it to. Otherwise, you can use this method with a request object that does not have a predicate, in order to fetch all contacts.

unifiedContactWithIdentifier(_:keysToFetch:) method of CNContactStore

This fetches only a single contact with a given identifier, if it can find one. Use this method to fetch properties for a partially fetched contact.

NOTE

The term “unified contacts” is iOS’s way of showing that the contact objects that we get are intelligently merged from different sources, if available. If you have “Foo bar” in your contacts and then you sign into FaceBook with their iOS app and bring your FaceBook contacts into your phone, and you have “Foo bar” on FaceBook as well, iOS will merge that contact for you into one contact. Foo bar is now a unified contact.

Discussion

Le’ts have a look at a few examples. First, let’s write some code that will find anybody in our address book whose name matches “John”. We start off by creating a predicate using the predicateForContactsMatchingName(_:) class method of the CNContact class.

let predicate = CNContact.predicateForContactsMatchingName("john")

Then we are going to specify that we need the first and the last name of the contacts that match that name:

let toFetch = [CNContactGivenNameKey, CNContactFamilyNameKey]

Once that is done, use the unifiedContactsMatchingPredicate(_:keysToFetch:) method of the contact store to fetch the contacts matching my predicate. Go through all matching contacts and print their first and last name alongside their identifier property.

do{

let contacts = try store.unifiedContactsMatchingPredicate(

predicate, keysToFetch: toFetch)

for contact in contacts{

print(contact.givenName)

print(contact.familyName)

print(contact.identifier)

}

} catch let err{

print(err)

}

NOTE

I’ve wrapped this whole code inside NSOperationQueue().addOperationWithBlock(_:) to make sure that I am doing the search on a background thread. I suggest that you do that too.

Every contact object has a handy property called identifier. This identifier usually looks like a UUID. If you keep an identifier to a contact, you can always refetch that contact using the unifiedContactWithIdentifier(_:keysToFetch:) method of CNContactStore. You do not have to explicitly fetch the identifier property of a contact. This identifier is fetched whether you want it or not, for every contact that you get from a store. So you can omit that in your keysToFetch.

Let’s look at another example. This time we are going to do the same thing that we did in the previous example, but instead, use the CNContactFetchRequest class mixed with the enumerateContactsWithFetchRequest(_:usingBlock:) method of CNContactStore to achieve the same results:

First again I am going to specify what properties in the contacts I am interested in reading:

let toFetch = [CNContactGivenNameKey, CNContactFamilyNameKey]

I will now construct my fetch request using these properties:

let request = CNContactFetchRequest(keysToFetch: toFetch)

Then I will fetch the contacts with the aforementioned method:

do{

try store.enumerateContactsWithFetchRequest(request) {

contact, stop in

print(contact.givenName)

print(contact.familyName)

print(contact.identifier)

}

} catch let err{

print(err)

}

The block that you pass to this method has two parameters. The first is the contact. The second is a Boolean pointer that you can set to true whenever you want to exit this enumeration. You can do that like this:

stop.memory = true

How about looking at another example. Let’s say that you want to fetch all contacts whose name is similar to “Foo”. You then want to find out whether they have a profile photo. If they do, we will refetch those contacts and get their profile photo. The purpose of this exercise is to show you that if you are interested in contacts with photos, it is best to first see whether they have photos and only if they do, fetch their profile photos. I’ll start off by defining the keys that I want to fetch and I ask for a key that tells me whether a contact has a photo:

var toFetch = [CNContactImageDataAvailableKey]

Then I will define my predicate:

let predicate = CNContact.predicateForContactsMatchingName("foo")

Next, I will find all contacts that match my predicate:

let contacts = try store.unifiedContactsMatchingPredicate(predicate,

keysToFetch: toFetch)

NOTE

The previous statement must be wrapped inside a do{}catch{} block, otherwise it won’t compile. I am not writing that statement here in the book because I want to explain the code step by step. If I paste the do{}catch{}, I’ll have to paste the whole code in a gigantic block and that’s not very good.

Now that we have our contacts, let’s go through them and only find the ones that do have an image:

for contact in contacts{

guard contact.imageDataAvailable else{

continue

}

...

The CNContact class offers a isKeyAvailable(_:) method that returns true or false depending on whether a given key is available for access on a contact or not. So here I am going to ask whether my contacts have images (the CNContactImageDataKey key) and if they do, I am going to read the image:

//have we fetched image data?

if contact.isKeyAvailable(CNContactImageDataKey){

print(contact.givenName)

print(contact.identifier)

print(UIImage(data: contact.imageData!))

} else {

...

NOTE

None of our contacts at this point will have images because we have not fetched the images yet in our original fetch request. This is for demonstration purposes really and to teach you how to use the isKeyAvailable(_:) method.

If the contacts don’t have their image data available at this point (which they won’t!), we will use the identifier of each one of them and re-fetch them, but this time by specifying that we need the image data as well:

else {

toFetch += [CNContactImageDataKey, CNContactGivenNameKey]

do{

let contact = try store.unifiedContactWithIdentifier(

contact.identifier, keysToFetch: toFetch)

print(contact.givenName)

print(UIImage(data: contact.imageData!))

print(contact.identifier)

} catch let err{

print(err)

}

}

And that was it, really. If you have the identifier of a contact, you can fetch that contact quite easily, as we saw. Now let’s say that you do have this identifier saved somewhere inside your app and you want to directly fetch that contact. We do that using theunifiedContactWithIdentifier(_:keysToFetch:) method of the contact store:

NSOperationQueue().addOperationWithBlock{[unowned store] in

let id = "AECF6A0E-6BCB-4A46-834F-1D8374E6FE0A:ABPerson"

let toFetch = [CNContactGivenNameKey, CNContactFamilyNameKey]

do{

let contact = try store.unifiedContactWithIdentifier(id,

keysToFetch: toFetch)

print(contact.givenName)

print(contact.familyName)

print(contact.identifier)

} catch let err{

print(err)

}

}

See Also