Contacts Framework Vs AddressBook Swift 3.0

Contacts Framework

Contacts Framework came up  with iOS 9.0 . This framework provide Swift and Objective-C API to access the user’s contact information.

AddressBook Framework

AddressBook Framework was used in iOS 8 and earlier for access user’s contact information .

Through this blog you will get to understand working with Contacts and AddressBook Framework.  So before accessing the contact you have to take permission from user to access contact.

Permissions:

There are four type of permission states:

  1. Not determined:  When user installs the app and open for the first time.
  2. Denied: When user denied to access contact.
  3. Restricted:  This state can not be handle by user. This might be another reason to be restricted.
  4. Authorized:  When user allows to access contact.

Ask permission from user to access contact:

Contacts Framework:

public func checkPermissions() throws {

    let status = CNContactStore.authorizationStatus(for: .contacts)
    switch status {
    case .denied:
        throw ContactAccessError(description: "Access Denied")
    case .notDetermined:
        // In case of not determined request for access
        // If allowed it will return success otherwise return error
        contactStore.requestAccess(for: .contacts, completionHandler:{ success, error in
            if success {
                print("Access Allowed")
            }
        })       
    default:
        break
    }
}

In Contact Framework CNContactStore provides ways to execute fetch and save requests. authorizationStatus(for:)  returns the current authorization status to access the contact data. requestAccess(for:completionHandler:) requests access to user’s contacts.  ContactAccessError  is a custom error.

class ContactAccessError: LocalizedError {
    
    private var desc = ""
    
    init(description: String) {
        desc = description
    }
    
    var errorDescription: String? {
        get {
            return self.desc
        }
    }
}

 

AddressBook Framework:

 func contactsPermission(completion: @escaping (Bool) -> Void) {
    let authorizationStatus = ABAddressBookGetAuthorizationStatus()
    switch authorizationStatus {
    case .authorized:
        completion(true)
            
    case .restricted:
        completion(false)
            
    case .denied:
        completion(false)
            
    case .notDetermined:
        self.getAccessOfAddressBook()
    }
  }

  func getAccessOfAddressBook() {
        let addressBookRef: ABAddressBook = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue()
        ABAddressBookRequestAccessWithCompletion(addressBookRef, {(granted: Bool, error: CFError?) in
            if granted{
                print("Access granted")
            } else {
                print("Access not granted")
            }
        })
    }

In AdressBook  ABAddressBookGetAuthorizationStatus() return current authorization status. if you will open the application first time, then the auterization status will be notDetermined. So in this case you have to request to access contact. In AddressBook Framework first you have to get the reference of AddressBook to request for access contacts.

ABAddressBookCreateWithOptions(_ options:, _ error: )  creates a new address book object (reference) with data from the Address Book database. This object is unmanaged( ie memory management is not controlled by the Swift runtime system). 

takeRetainedValue() get the value of an unmanaged reference as a managed reference (ie you want ARC to take care of releasing the object when you’re done).

ABAddressBookRequestAccessWithCompletion(_ addressBook: , _ completion: ) requests access to user’s contacts.

Note: One more thing you will have to do in your project to access user’s contact is:-     define Privacy – Contacts Usage Description key in info.plist file with description.

When you run the app for the first time and try to save, fetch or update contact, you should see the following alert.


What happens if the user doesn’t allow access?

If  user don’t allow to access contact, in this case  your app will stuck forever. So in case of denied you can so an alert  to user to allows access contact.

func showPermissionAlert() {
    let alert = UIAlertController(title: "", message: "Allow App to access contacts"
        , preferredStyle: .alert)
    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) 
    let settingAction = UIAlertAction(title: "Settings", style: .default, handler: { (action) in
          // Open Settings, right to the page with your app’s permissions
          openSetings()
    })
    
    alert.addAction(cancelAction)
    alert.addAction(settingAction)
    alert.preferredAction = settingAction
   self.present(alert, animated: true, completion: nil)   
}

func openSettings() {
   let url = URL(string: UIApplicationOpenSettingsURLString)
   UIApplication.shared.openURL(url!)
 }

This will send the user to the Settings app, right to the page with your app’s permissions.

Save New Contacts

Contacts Framework:

Here I have created an object of model class ContactDetail which holds the detail of new contact like first name, last name, contact number, email etc.

 contactInfo = ContactDetail(firstName: textFieldFirstName.text, lastName: textFieldLastName.text, PhnNo: textFieldPhoneNumber.text, street: textFieldStreet.text, city: textFieldCity.text, state: textFieldState.text, zipCode: textFieldZipCode.text, homeEmail: textFieldHomeEmail.text, workEmail: textFieldWorkEmail.text)

The below function is for creating the new contact.  First you have  to create a CNMutableContact object and assign the givenName, familyName etc which you want to save.  For contact properties that can have multiple values, such as phone numbers or email addresses, an array of CNLabeledValue objects is used.  Here CNPhoneNumber class  returns a new phone number object initialized with the specified phone number string.

 func createContact() {
    let contact = CNMutableContact()
    contact.givenName = contactInfo.firstName
    contact.familyName = contactInfo.lastName
    contact.phoneNumbers = [CNLabeledValue(label: CNLabelPhoneNumberiPhone, value: CNPhoneNumber(stringValue: contactInfo.PhnNo))]
        
    let homeAddress = CNMutablePostalAddress()
    homeAddress.street = contactInfo.street
    homeAddress.city = contactInfo.city
    homeAddress.state = contactInfo.state
    homeAddress.postalCode = contactInfo.zipCode
    contact.postalAddresses = [CNLabeledValue(label:CNLabelHome, value:homeAddress)]
        
    let homeEmail = CNLabeledValue(label: CNLabelHome, value: (contactInfo.homeEmail) as NSString!)
    let workEmail = CNLabeledValue(label: CNLabelWork, value: (contactInfo.workEmail) as NSString!)
    contact.emailAddresses = [homeEmail, workEmail]
        
    // Saving the newly created contact
    saveContact(contact: contact) { [weak self] result in
        if result {
           // show success alert
           let contactDetailVC = CNContactViewController(for: contact)
           contactDetailVC.contactStore = CNContactStore()
           contactDetailVC.title = "Contact Info"
           self?.navigationController?.pushViewController(contactDetailVC, animated: true)
        } else {
           // show something went wrong
        }
    }      
}

Note: Here CNContactViewController(for: contact) is used to show saved contact . This class   is of ContactsUI Framework. So you have to import ContactsUI framework.  The CNContactViewController  class implements the view to display a contact. CNContactViewController can display a new contact, unknown contact, or existing contact.

import ContactsUI

 

Note: To proceed any operation on contact like save, fetch, update or delete,  first you have to check permission by using checkPermission() method (discussed above). if access to contact is allowed then the process will proceed further otherwise catch block will execute and an alert will show to allow access contact (showPermissionAlert() discussed above).

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

 func saveContact(contact: CNMutableContact, completion: @escaping (Bool) -> Void) {  
    do {
        try checkPermissions()
        
        // Adds the specified contact to the contact store
        let saveRequests = CNSaveRequest()
        let contactStore = CNContactStore()
        saveRequests.add(contact, toContainerWithIdentifier:nil)
        do {
            
            // Executes a save request and returns success or failure
            try contactStore.execute(saveRequests)
            completion(true)
            
        } catch {
            completion(false)
        }
        
    } catch {
        // in case of access denied this block will execute
        showPermissionAlert()
    }    
}

AddressBook:

The call to ABPersonCreate() creates a reference to a new Address Book record. Once you have that, you can call ABRecordSetValue() and pass it the record, the field to change, and the value. Since one contact can have multiple phone numbers (home, mobile, etc.) and emails, you have to use ABMutableMultiValue  reference, which supports multiple values.  ABMultiValueAndLabel adds a value and its corresponding label to a multivalue property (ie phoneNumbers). The label kABPersonPhoneMainLabel says that this is the contact’s primary number. You can add another label as your requirements. The same step will be follow to create email. ABAddressBookAddRecord()  adds a record to addressBookRef.

private func createContact() {
    let addressBookRef: ABAddressBook = ABAddressBookCreateWithOptions().takeRetainedValue() 
    let contactDetail: ABRecord = ABPersonCreate().takeRetainedValue()
    ABRecordSetValue(contactDetail, kABPersonFirstNameProperty, contactInfo?.firstName as CFTypeRef!, nil)  
    ABRecordSetValue(contactDetail, kABPersonLastNameProperty, contactInfo?.lastName as CFTypeRef!, nil)

    let phoneNumbers: ABMutableMultiValue = ABMultiValueCreateMutable(ABPropertyType(kABMultiStringPropertyType)).takeRetainedValue()
    ABMultiValueAddValueAndLabel(phoneNumbers, contactInfo?.PhnNo as CFTypeRef!, kABPersonPhoneMainLabel, nil)
    ABRecordSetValue(contactDetail, kABPersonPhoneProperty, phoneNumbers, nil) 

    let homeEmail:ABMutableMultiValue = ABMultiValueCreateMutable(ABPropertyType(kABMultiStringPropertyType)).takeRetainedValue()
    ABMultiValueAddValueAndLabel(homeEmail, contactInfo.homeEmail as CFTypeRef! , kABHomeLabel , nil)
    ABRecordSetValue(contactDetail, kABPersonEmailProperty, homeEmail , nil)
    
    let workEmail:ABMutableMultiValue = ABMultiValueCreateMutable(ABPropertyType(kABMultiStringPropertyType)).takeRetainedValue()
    ABMultiValueAddValueAndLabel(workEmail, contactInfo.homeEmail as CFTypeRef! , kABWorkLabel , nil)
    ABRecordSetValue(contactDetail, kABPersonEmailProperty, workEmail , nil)
    
    ABAddressBookAddRecord(addressBookRef, contactDetail, nil)
    saveContact(addressBookRef: addressBookRef, contact: contactDetail)
}

Now contact is only added to reference of Address Book. It is not saved in Address Book database. To save in Address Book implement saveContact().  ABAddressBookHasUnsavedChanges() indicates whether an address book has changes that have not been saved to the Address Book database.  ABAddressBookSave() save the changes in Address Book.

func saveContact(addressBookRef: ABAddressBook, contact: ABRecord) {
    if ABAddressBookHasUnsavedChanges(addressBookRef){
        let savedToAddressBook = ABAddressBookSave(addressBookRef, nil)

        if savedToAddressBook {         
            let alertView = UIAlertController(title: nil, message: "Contact saved Successfuly.", preferredStyle: .alert)
            alertView.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
                let personViewController = ABPersonViewController()
                personViewController.displayedPerson = contact
                self.navigationController?.pushViewController(personViewController, animated: true)
            }))
            
            present(alertView, animated: true, completion: nil)
    
        } else {
            print("Couldn't save changes.")
        }
    } else {
        print("No changes occurred.")
    }
}

Note: Here personViewcontroller is used to display saved contact which is the part of  AddressBookUI. So you have to import it .

import AddressBookUI

Fetch Contacts

Contacts Framework:

func fetchContactsFromPhonebook() -> [CNContact] {
    let name = textFieldName.text ?? ""
    let keysToFetch = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
                       CNContactPhoneNumbersKey, CNContactEmailAddressesKey] as [Any]
    
    
    return searchContacts(withName: name, keysToFetch: keysToFetch)
}

 

func searchContacts(withName: String, keysToFetch: [CNKeyDescriptor]) -> [CNContact] {
    
    var results: [CNContact] = []
    do {
        try checkPermissions()
        
        // Fetch data using some predicate
        let predicate: NSPredicate = CNContact.predicateForContacts(matchingName: withName)
        
        // if you want to fetch only some record of a contact.
        // Then define key to fetch
        let keys = keysToFetch
        
        do {         
            // Fetches all unified contacts matching the specified predicate
            let containerResults = try contactStore.unifiedContacts(matching: predicate, keysToFetch: keys)
            results.append(contentsOf: containerResults)
            
        } catch {
            print(error.localizedDescription)
        }
    } catch {
        showPermissionAlert()
    }
    return results
}

 

As discussed earlier you can fetch contacts using the contact CNContactStore(), which represents the user’s contacts database. You can add some predicate(any string or character) to fetch contacts. CNContact provides predicates for filtering the contacts you want to fetch .

You can use keysToFetch to limit the contact properties that are fetched. For example if you want to fetch only name , phone number and email address then pass these  key in keyToFetch.

The Contacts framework can also perform operations on the fetched contacts, such as formatting contact names. Here in code there is a key  CNContactFormatter.descriptorForRequiredKeys(for: .fullName) in keyToFetch array. This will format the name of contact. For example if fetched contact givenName = “Jhon” and familyName = “Rich” then CNContactFormatter format the fullName = “Jhon Rich”.

unifiedContactsMatchingPredicate(_:keysToFetch:) method of CNContactStore unified the result. That is 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.

AddressBook Framework:

func fetchContactFromAddressBook() -> [ABRecord] {
    var result = [ABRecord]()
    var index = 0
    let addressBookRef = ABAddressBookCreate().takeRetainedValue()
    let allContacts = ABAddressBookCopyArrayOfAllPeople(addressBookRef).takeRetainedValue() as Array
    
    for record in allContacts {
        let currentContact: ABRecord = record
        let currentContactName = ABRecordCopyCompositeName(currentContact).takeRetainedValue() as String
        if currentContactName.contains(textFieldName.text!) {
            result.append(currentContact)
            index += 1
        }
    }
    return result
}

In AddressBook framework  you can fetch contact by reference of AddressBook .As discussed earlier  ABAddressBookCreate give the reference ok AddressBook. Tp fetch all record yo can call  ABAddressBookCopyArrayOfAllPeople() methodIn AddressBook framework there is no any method to apply predicate . so you have to write the code to compare to find particular result.

Delete Contact

Contacts Framework:

 func deleteContact() {   
    do {
         try checkPermissions() 
         let store = CNContactStore()
         let saveRequest = CNSaveRequest()
         saveRequest.delete(newContact)
         do {
              try store.execute(saveRequest)
        
         } catch {
              print(error)
         }
     } catch {
         showPermissionAlert()
     }
}

Note: In contacts Framework there is delete(_ contact:) method to delete contact. the contact fetched is of CNContact type . So you have to convert it into CNMutableContact then pass in delete() method.

let newContact = contactDetail?.mutableCopy() as! CNMutableContact

AddressBook Framework:

private func deleteContact() {
    let isRemove = ABAddressBookRemoveRecord(addressBookRef, currentContact, nil)
    print(isRemove)
    let isSaved = ABAddressBookSave(addressBookRef, nil)
    if isSaved {
        let alertView = UIAlertController(title: nil, message: "Contact deleted Successfully.", preferredStyle: .alert)   
        alertView.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        present(alertView, animated: true, completion: nil)      
    }
}

In AddressBook Framework call  ABAddressBookRemoveRecord(_ addressBook: , _ record: , _ error:) will delete record from addressbookRef but not from the AddressBook database. So after remove call the ABAddressBookSave(_ addressBook:, _ error: ) method to reflect changes in database.

Update Contact

Contacts Framework:

 func updateContact() {
    do {
         try checkPermissions()
         let store = CNContactStore()
         let saveRequest = CNSaveRequest()
    
          saveRequest.update(newContact)
          do {
               try store.execute(saveRequest)
        
           } catch {
                print(error)
           }

     } catch {
          showPermissionAlert()
     }
}

Similar to delete there is update(_ contact:)  method in contact Framework to update existing contact.

AddressBook Framework:

To update contact in AddressBook you can use the method ABAddressBookHasUnsavedChanges() of ABAddressBook. If it returns true then call save method to save changes. (Similar to Add new contact).

Leave a Reply