Managing contacts in Swift, AddressBook and Contacts frameworks - Digital Leaves
1260
post-template-default,single,single-post,postid-1260,single-format-standard,ajax_fade,page_not_loaded,,select-theme-ver-3.8,wpb-js-composer js-comp-ver-5.1.1,vc_responsive

Managing contacts in Swift, AddressBook and Contacts frameworks

Accessing contacts in swift is not as hard as dealing with cryptography on iOS, but the AddressBook framework has never been specially developer-friendly, and the strongly typed nature of Swift and its deliberate distance to C makes it even more tedious to use. Luckily for us, Apple introduced the Contacts framework with iOS 9, but there’s still many chances that you will need to support iOS 8 and prior.

In this article, we are going to delve into both frameworks and compare them in terms of convenience and ease of use in swift. At the end of the day, both frameworks work in quite a similar way, and both will allow us to access and modify the same information, so it’s a matter of whether you need to support iOS 8 devices or not.

Our sample application

Our sample application it’s a really simple app with three main screens. The first one will allow us to choose between using the AddressBook or Contacts frameworks. The second screen will retrieve our device’s contacts and show them in a (more or less) fancy way, and the third one will allow us to create a new contact.

contactsApp

We will set our application for targeting iOS 8+ devices, and we will use the #available(…) directive to check if the iOS version of the device is at least 9.0, so we can access the Contacts framework functionality.

The first screen is just two buttons that will take us to either our AddressBookViewController (that will use the old, C-Style AddressBook framework) or the ContactsViewController (that will use the new Contacts framework). Both of them will have a “Create” button to create a new contact, that will take us to the CreateContactViewController. This contact will handle the creation in both frameworks.

appStructure

As always, you can download the full project from my Github repository to follow along the article.

Obtaining access

For being able to access the contacts in iOS to perform any kind of CRUD operation, we need to ask for permission first from the user. This works similarly in both frameworks:

The AddressBook framework has a function called ABAddressBookGetAuthorizationStatus() that returns the current ABAuthorizationStatus (authorization status for the address book), with the following values:

  • NotDetermined
  • Restricted
  • Denied
  • Authorized

Both Restricted and Denied, though different in meaning, indicate that the user has denied access to the contacts in the device, so we won’t be able to use them. NotDetermined means that we have not yet requested permission from the user, and Authorized indicates that we are good to access the contacts.

Getting the current authorization status is really simple in Swift:

If we have not requested permission yet, we need to do so by calling ABAddressBookRequestAccessWithCompletion(ABAddressBook, completion). This method will set the proper ABAuthorizationStatus depending on the user’s response, and the completion block will be executed, including a “granted” bolean that indicates if the user granted access to the contacts.

The Contacts framework has essentially the same authorization values, and uses the following code for checking the permission status:

AddressBooks vs ContactStores

Once we have been granted access by the user, we are authorized to access the contacts. This access is done by means of an ABAddressBook (in the AddressBook framework) or the CNContactStore (the the Contacts framework). Essentially they represent the same concept, the book or store where the contacts are contained. The first is obtained by a call to ABAddressBookCreateWithOptions, and taking care of retaining the object in ARC:

The second one is simply obtained by means of a CNContactStore() creation call:

var contactStore = CNContactStore()

In a production environment, you may want to initialize, handle and use both entities in background threads due to I/O access potentially blocking your main thread.

Getting contacts

For our sample application, let’s just create a very simple contact entity called ContactEntry, with a name, email, phone and image:

We will define initializers for creating a ContactEntry with data from contacts in both the AddressBook and Contacts frameworks.

ABRecord

Contacts in the AddressBook framework are represented by the ABRecord entity, which is nothing more than a good old CFTypeRef. This entity is composed of several single and multi-valued references for the different attributes of the person it represents. Single values include the first name or the notes for the person, whereas multiple values include things like emails and phones. You have the full reference of all values here. To get a single or multiple value from an ABRecord, you do it with ABRecordCopyValue(ABRecord, property).

This function returns the value for that property, which is not always a basic type. For single types, like notesRef in the example, you get a CFString that can be converted to String with a simple cast. In the case of a multiple value, like emailsMultiValueRef, you get a reference to an ABMultiValue, that can be exported to an array by using ABMultiValueCopyArrayOfAllValues(ABMultiValue).

It’s important to understand these different properties, and to know what type of result to expect from them. For instance, if you get the array of emails with the kABPersonEmailProperty property, you will get also some unexpected results from the sample Apple users, like “www.icloud.com”, that doesn’t resemble an email at all. This is why we are using the filtering function isEmail() to check that the value is actually an email.

There are also some special methods for getting the full name or the image of a person. For the full name, you use ABRecordCopyCompositeName(ABRecord):

For getting the picture of the contact, you get the raw NSData with ABPersonCopyImageData(ABRecord). You can check beforehand if a concrete reference of ABRecord has a valid picture assigned with the function ABPersonHasImageData(ABRecord):

Putting it all together, we have the init method for ContactEntry with an ABRecord reference:

There are many ways in which we can retrieve contacts from the AddressBook framework, the most usual of them being ABAddressBookCopyArrayOfAllPeople(ABAddressBook). Don’t forget to wrap this call in a ABAddressBookRequestAccessWithCompletion.

The AddressBookViewController will contain a UITableView that will display the contacts with an image, the name, and optionally the email and phone. For retrieving the list of contacts, we’ll use the method retrieveAddressBookContacts.

This method will copy all the contacts from the address book and generate an array of ContactEntry instances. This array will serve as the data source for the table.

CNContact

The CNContact entity works similarly to an ABRecord. It has a number of keys for representing the different attributes of a contact. Not all CNContact instances will have all the keys present, and you can check that with the method cnContact.isKeyAvailable(key) before trying to access that property. The rule of thumb is that if you want to check for the property xxxx of a contact, you can call cnContact.isKeyAvailable(CNContactXxxxKey).

CNContact doesn’t have the full name retrieval method equivalent of ABRecordCopyCompositeName, but we can easily retrieve the properties givenName and familyName to compose the full name. Properties returned by CNContact getters can either be simply strings (like in the familyName example), or can be CNLabeledValue for array (multiple value) types.

This CNLabeledValue type is a CNContact abstraction for representing any type of property for contacts. Multiple values like emailAddresses and phoneNumbers return these CNLabeledValues (as an array). The CNLabeledValue has a “value” property that allows you to access the value contained therein, and this value can be a String (like in the emailAddress array), or another CN-type like CNPhoneNumber (for example, in the case of the phoneNumbers). CNPhoneNumber has a stringValue property to easily get the phone number as a convenient String.

With all this information in mind, we can build our init method to initialize a ContactEntry with a CNContact retrieved from a CNContactStore.

Note how we tagged the method with @available(iOS 9.0, *) to avoid the debugger from complaining about the method accessing functionality only available for iOS 9 and up. Also, as the values retrieved from emailAddresses have the same problem as the ones retrieved using the AddressBook framework (they can contain non-email addresses) we need to make sure to check that there’s at least a valid email contained on them.

As in the AddressBook framework, retrieving contacts in the Contacts framework can be done by many methods. There is a number of methods allowing you to use NSPredicate or identifiers to fetch records and groups, but my favorite method for just retrieving all the contacts (and iterating through them) is the enumerateContactsWithFetchRequest(fetchRequest, usingBlock block). This method takes a fetch request, in which you usually specify the properties that you want to retrieve from the contacts, and a block that receives every contact retrieved in a forEach fashion. Pretty convenient and easy to use.

In our example project, we are interested in the following keys:

  • CNContactGivenNameKey and CNContactFamilyNameKey: to get the full name of the contact.
  • CNContactImageDataAvailableKey and CNContactImageDataKey: the first one allows you to check if there’s an image associated with the contact (yes, you need to retrieve that independently), while the second contains the actual image data.
  • CNContactPhoneNumbersKey: an array of CNLabeledValue whose values are CNPhoneNumber instances.
  • CNContactEmailAddressesKey: an array of CNLabeledValue whose values are strings.

So here’s the final method for retrieving our contacts in our ContactsViewController using the Contacts framework:

Now that we know how to retrieve our contacts in both frameworks, let’s add a “Create” button and have a look at how do we create contacts in the different frameworks:

Creating contacts

Creating contacts is also really similar between both frameworks.

AddressBook framework

For creating a new AddressBook entry, we need to use the ABPersonCreate() method, taking care of retaining the resulting variable for ARC memory management:

Once we have this new contact, we can start adding properties by using the ABRecordSetValue(ABRecordRef, property, value, error) method. It will return a boolean value indicating if the operation was successful. For example, for setting the first name of a new person, we could use:

Remember, however, that some values in ABRecord were multiple-values properties like emails or phone numbers. For setting those, we need to create a ABMutableMultiValue with the method ABMultiValueCreateMutable(propertyType). The propertyType specifies the type of the values contained inside this multi value, and gets specified (more precisely, created) by using the method ABPropertyType(propertyNameLabel), where label is a key like kABMultiStringPropertyType. Seems convoluted, huh? Yes, agreed, but you will see it clearer with the example below.

Once created, we need to add the value to that ABMutableMultiValue entity by using ABMultiValueAddValueAndLabel(multiValue, value, label, error). The value must be convertible to the propertyType specified in AMBultiValueCreateMutable. Labels are keys used to specify the type of property included in the value, like kABHomeLabel (for a home phone number) or kABWorkLabel (for a work email address for instance).

So, bringing it all together, let’s see how to add a home phone number to our newly created contact:

For setting the image, we use a different method called ABPersonSetImageData()

Finally, we need to add the record to the address book and save it. This is done by two methods: ABAddressBookAddRecord and ABAddressBookSave respectively. There’s a very handy method called ABAddressBookHasUnsavedChanges we can call to know if we need to do the save call. So this is how we would specify that in our example project:

Contacts framework

For the Contacts framework we follow the same strategy, we create the CNLabeledValues for the new contact (that’s actually the mutable CNMutableContact version of our contact), add that to the newly created contact, add it to the CNContactStore and save it. The only change is the more modern syntax associated with the framework:

Notice how in this case the saving operation doesn’t require us to add the contact to a CNContactStore, we save the contact by creating a CNSaveRequest, associating the newly created contact to that request, and calling executeSaveRequest(request) with that request on the CNContactStore.

And that’s all. Please, don’t forget to download the sample project to see all that’s explained in this article in action. Is there anything missing? Want to add anything? Let us know in the comments.

10 Comments
  • Ranvijai Singh

    July 8, 2016 at 6:57 am Reply

    Hi I am stuck with one issue. I create a new contact with Contacts framework and set the image data and save it. It works completely fine. After fetching the contact image data is there but the thumbnail image is not coming.
    In second case i took a existing contact and set a picture and update. Now if i fetch again i am getting the image data as well as thumbnail image. Code are same for saving image in both scenario. Can you plz help me why the thumbnail image is not coming in first place. You can also try the same behaviour.

    • rym

      November 21, 2016 at 10:55 am Reply

      I have the same issue with thumbnail.

    • Ignacio Nieto Carvajal

      December 6, 2016 at 10:55 am Reply

      Hi Ranvijai, Rym. Sorry for my late reply. I can’t reproduce the exact behaviour that you describe. If I create a new contact and set the image, when I go back to the list of contacts, the image of the contact is clearly shown.

      Can you please elaborate on what’s exactly the problem? You don’t see the image?

  • Chuy

    September 21, 2016 at 6:54 pm Reply

    How can I do to delete some contact? is it possible?

    • rym

      November 21, 2016 at 10:56 am Reply

      is it possible? , did you come out with a solution?

      • Ignacio Nieto Carvajal

        December 6, 2016 at 11:03 am Reply

        Hi Rym, Chuy.

        For deleting contacts in the Contacts framework, you have to perform a query request to the contacts store with the unifiedContactsMatchingPredicate method, using your predicate. Then you will get a contact, that once made mutable, you can do a CNSaveRequest and call deleteContact. The process would be similar to this:

  • Amanpreet Singh

    December 1, 2016 at 9:44 am Reply

    Can you please update this for swift 3. I am getting bad access in the following code :
    let allContacts = ABAddressBookCopyArrayOfAllPeople(self.addressBookRef).takeRetainedValue() as Array.
    Can you please tell me what is wrong in this statement for swift 3.

    • Ignacio Nieto Carvajal

      December 6, 2016 at 11:06 am Reply

      Hi Amanpreet,
      Sorry for my late reply. The code was adapted to swift 3. The problem was that, after iOS 10, it’s required to include a key called “NSPhotoLibraryUsageDescription” with the description for a request for permission to the user, in order to be able to access the photo library. It should work now if you download it again from the repository.

  • Techwizardg

    March 30, 2017 at 7:04 am Reply

    Hi ,

    We build iOS Apps using objective-c. For our app we need to read the contacts from addressbook and show the same in our app with some additional attributes. We have checked in phones with 5000 contacts and above, we seem to get memory error. We are doing the following

    We are reading 500 contacts from ios addressbook at a time and at the same time if the user does a search on a particular character we query for the same and add to our app address book data structure and keep populating this in a background task. Is this approach

  • Karamjeet Singh

    June 20, 2017 at 5:18 am Reply

    Hi,

    Can you please help me to Add the contacts in default contacts from app with the feature of open url. As like WhatsApp , WhatsApp icon is shown in front of contact (default contacts app) and ability to make call or message.

    Thanks!

Post a Comment

Before you continue...

Hi there! I created the Digital Tips List, the newsletter for Swift and iOS developers, to share my knowledge with you.


It features exclusive weekly tutorials on Swift and iOS development, Swift code snippets and the Digital Tip of the week.


Besides, If you join the list you'll receive the eBook: "Your First iOS App", that will teach you how to build your first iOS App in less than an hour with no prior Swift or iOS knowledge.

I hate spam, and I promise I'll keep your email address safe.