Search code examples
iosswiftios9contacts

iOS Contacts How to Fetch contact by phone Number


I just want to get contact given name and family name by phone number. I tried this but this is too much slow and cpu is hitting over %120.

let contactStore = CNContactStore()
            let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey]
            var contacts = [CNContact]()
            do {
                try contactStore.enumerateContactsWithFetchRequest(CNContactFetchRequest.init(keysToFetch: keys), usingBlock: { (contact, cursor) in
                    if (!contact.phoneNumbers.isEmpty) {
                        for phoneNumber in contact.phoneNumbers {
                            if let phoneNumberStruct = phoneNumber.value as? CNPhoneNumber {
                                do {
                                    let libPhone = try util.parseWithPhoneCarrierRegion(phoneNumberStruct.stringValue)
                                    let phoneToCompare = try util.getNationalSignificantNumber(libPhone)
                                    if formattedPhone == phoneToCompare {
                                        contacts.append(contact)
                                    }
                                }catch {
                                    print(error)
                                }
                            }

                        }
                    }
                })
                if contacts.count > 0 {
                    contactName = (contacts.first?.givenName)! + " " + (contacts.first?.familyName)!
                    print(contactName)
                    completionHandler(contactName)
                }
            }catch {
                print(error)
            }

Also When I Use phonenumber Kit for find contacts its increasing cpu and giving late response.

var result: [CNContact] = []
        let nationalNumber = PhoneNumberKit().parseMultiple([phoneNumber])
        let number = nationalNumber.first?.toNational()
        print(number)

        for contact in self.addressContacts {
            if (!contact.phoneNumbers.isEmpty) {

                let phoneNumberToCompareAgainst = number!.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).joinWithSeparator("")
                for phoneNumber in contact.phoneNumbers {
                    if let phoneNumberStruct = phoneNumber.value as? CNPhoneNumber {
                        let phoneNumberString = phoneNumberStruct.stringValue
                        let nationalContactNumber = PhoneNumberKit().parseMultiple([phoneNumberString])
                        let nationalContactNumberString = nationalContactNumber.first?.toNational()
                        if nationalContactNumberString == number {
                            result.append(contact)
                        }
                    }
                }
            }
        }

        return result

Solution

  • The problem with your implementation is that you access the address book in every search you are making.

    If instead you will hold in-memory the address book content after the first access you will not reach this high CPU usage.

    1. First hold a lazy var in your controller that will hold the address book content:

      lazy var contacts: [CNContact] = {
          let contactStore = CNContactStore()
          let keysToFetch = [
              CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName),
              CNContactEmailAddressesKey,
              CNContactPhoneNumbersKey,
              CNContactImageDataAvailableKey,
              CNContactThumbnailImageDataKey]
      
          // Get all the containers
          var allContainers: [CNContainer] = []
          do {
              allContainers = try contactStore.containersMatchingPredicate(nil)
          } catch {
              print("Error fetching containers")
          }
      
          var results: [CNContact] = []
      
          // Iterate all containers and append their contacts to our results array
          for container in allContainers {
              let fetchPredicate = CNContact.predicateForContactsInContainerWithIdentifier(container.identifier)
      
              do {
                   let containerResults = try     contactStore.unifiedContactsMatchingPredicate(fetchPredicate, keysToFetch: keysToFetch)
                  results.appendContentsOf(containerResults)
              } catch {
                  print("Error fetching results for container")
              }
          }
      
          return results
      }()
      
      1. Iterate through the in-memory array when you are looking for a contact with a specific phone number:

      .

         func searchForContactUsingPhoneNumber(phoneNumber: String) -> [CNContact] {
          var result: [CNContact] = []
      
          for contact in self.contacts {
              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 {
                              result.append(contact)
                          }
                      }
                  }
               } 
          }
      
          return result
      }
      

    I tested it with a very big address book, it works smoothly.

    Here is the entire view controller patched together for reference.

    import UIKit
    import Contacts
    
    class ViewController: UIViewController {
    
        lazy var contacts: [CNContact] = {
            let contactStore = CNContactStore()
            let keysToFetch = [
                    CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName),
                    CNContactEmailAddressesKey,
                    CNContactPhoneNumbersKey,
                    CNContactImageDataAvailableKey,
                    CNContactThumbnailImageDataKey]
    
            // Get all the containers
            var allContainers: [CNContainer] = []
            do {
                allContainers = try contactStore.containersMatchingPredicate(nil)
            } catch {
                print("Error fetching containers")
            }
    
            var results: [CNContact] = []
    
            // Iterate all containers and append their contacts to our results array
            for container in allContainers {
                let fetchPredicate = CNContact.predicateForContactsInContainerWithIdentifier(container.identifier)
    
                do {
                    let containerResults = try contactStore.unifiedContactsMatchingPredicate(fetchPredicate, keysToFetch: keysToFetch)
                    results.appendContentsOf(containerResults)
                } catch {
                    print("Error fetching results for container")
                }
            }
    
            return results
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
    
            let contact = searchForContactUsingPhoneNumber("(555)564-8583")
            print(contact)
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        func searchForContactUsingPhoneNumber(phoneNumber: String) -> [CNContact] {
    
            var result: [CNContact] = []
    
            for contact in self.contacts {
                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 {
                                result.append(contact)
                            }
                        }
                    }
                }
            }
    
            return result
        }
    }
    

    I used flohei's answer for the lazy var part.