iosswiftcncontactcontacts-framework

Swift Using Contacts Framework, search using phone number to get Name and User Image


I have an API that returns phone numbers in format: +1415xxxxxxx (E164)

Right now these numbers are put into a Cell of a UITableView and are presented as expected, however I'd like to be able to search the users contacts on the phone to see if the there is a match - if so also pass back the Firstname, last name and known photo.

Looking at the Apple pages (https://developer.apple.com/library/watchos/documentation/Contacts/Reference/Contacts_Framework/index.html) I need to

 import ContactsUI

but then Im unsure, do I load the contactDB into a dictionary and then search it? I can find lots of things on searching via name and less on searching via number:

  let predicate = CNContact.predicateForContactsMatchingName("Sam") 

Im trying to get to a function that I can call, that searches using the PhoneNumber and gives me back the FirstName, FamilyName and Image.

  func searchForContactUsingNumber(PhoneNumber: String)
 {

 // Search Via phoneNumber
  let store = CNContactStore()
  let contacts = try store.unifiedContactsMatchingPredicate(CNContact.predicateForContactsMatchingPhoneNumber(PhoneNumber), keysToFetch:[CNContactGivenNameKey, CNContactFamilyNameKey,CNContactImageData])

  return FirstName, GivenName,UIImage

 }

I get the feeling I'm going about this backwards but not sure which way is forwards.. Any ideas?


Solution

  • In order to get this example up-and-running quickly I used the following sources of info:

    Filter non-digits from string

    https://stackoverflow.com/a/32700339/558933

    http://www.appcoda.com/ios-contacts-framework/

    The code block below includes the authorisation check because I had to get it working in order to test in the simulator. The code is just the Single-View Apps view controller and you can connect up a UIButton in the Storyboard to the findContactInfoForPhoneNumber: method to get if to run. Output is to the console - you will need to replace these print statements with something else.

    If you are not interested in the full view controller code then just look at the searchForContactUsingPhoneNumber(phoneNumber: String) method. I've followed Apple's advice in the docs to run the CNContact framework asynchronously.

    The code strips all the +, - and ( symbols that could be in a phone number and just matches the digits so the phone number you pass in to match MUST be exactly the same.

    //
    //  ViewController.swift
    //  ContactsTest
    //
    //  Created by Robotic Cat on 13/04/2016.
    //
    
    import UIKit
    import Contacts
    
    class ViewController: UIViewController {
    
        // MARK: - App Logic
        func showMessage(message: String) {
            // Create an Alert
            let alertController = UIAlertController(title: "Alert", message: message, preferredStyle: UIAlertControllerStyle.Alert)
    
            // Add an OK button to dismiss
            let dismissAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { (action) -> Void in
            }
            alertController.addAction(dismissAction)
    
            // Show the Alert
            self.presentViewController(alertController, animated: true, completion: nil)
        }
    
        func requestForAccess(completionHandler: (accessGranted: Bool) -> Void) {
            // Get authorization
            let authorizationStatus = CNContactStore.authorizationStatusForEntityType(CNEntityType.Contacts)
    
            // Find out what access level we have currently
            switch authorizationStatus {
            case .Authorized:
                completionHandler(accessGranted: true)
    
            case .Denied, .NotDetermined:
                CNContactStore().requestAccessForEntityType(CNEntityType.Contacts, completionHandler: { (access, accessError) -> Void in
                    if access {
                        completionHandler(accessGranted: access)
                    }
                    else {
                        if authorizationStatus == CNAuthorizationStatus.Denied {
                            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                                let message = "\(accessError!.localizedDescription)\n\nPlease allow the app to access your contacts through the Settings."
                                self.showMessage(message)
                            })
                        }
                    }
                })
    
            default:
                completionHandler(accessGranted: false)
            }
        }
    
        @IBAction func findContactInfoForPhoneNumber(sender: UIButton) {
    
            self.searchForContactUsingPhoneNumber("(888)555-1212)")
        }
    
        func searchForContactUsingPhoneNumber(phoneNumber: String) {
    
            dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), { () -> Void in
                self.requestForAccess { (accessGranted) -> Void in
                    if accessGranted {
                        let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactImageDataKey, CNContactPhoneNumbersKey]
                        var contacts = [CNContact]()
                        var message: String!
    
                        let contactsStore = CNContactStore()
                        do {
                            try contactsStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: keys)) {
                                (contact, cursor) -> Void in
                                if (!contact.phoneNumbers.isEmpty) {
                                    let phoneNumberToCompareAgainst = phoneNumber.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).joinWithSeparator("")
                                    for phoneNumber in contact.phoneNumbers {
                                        if let phoneNumberStruct = phoneNumber.value as? CNPhoneNumber {
                                            let phoneNumberString = phoneNumberStruct.stringValue
                                            let phoneNumberToCompare = phoneNumberString.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).joinWithSeparator("")
                                            if phoneNumberToCompare == phoneNumberToCompareAgainst {
                                                contacts.append(contact)
                                            }
                                        }
                                    }
                                }
                            }
    
                            if contacts.count == 0 {
                                message = "No contacts were found matching the given phone number."
                            }
                        }
                        catch {
                            message = "Unable to fetch contacts."
                        }
    
                        if message != nil {
                            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                                self.showMessage(message)
                            })
                        }
                        else {
                            // Success
                            dispatch_async(dispatch_get_main_queue(), { () -> Void in
                                // Do someting with the contacts in the main queue, for example
                                /*
                                 self.delegate.didFetchContacts(contacts) <= which extracts the required info and puts it in a tableview
                                 */
                                print(contacts) // Will print all contact info for each contact (multiple line is, for example, there are multiple phone numbers or email addresses)
                                let contact = contacts[0] // For just the first contact (if two contacts had the same phone number)
                                print(contact.givenName) // Print the "first" name
                                print(contact.familyName) // Print the "last" name
                                if contact.isKeyAvailable(CNContactImageDataKey) {
                                    if let contactImageData = contact.imageData {
                                        print(UIImage(data: contactImageData)) // Print the image set on the contact
                                    }
                                } else {
                                    // No Image available
    
                                }
                            })
                        }
                    }
                }
            })
        }
    
    }