66

I am aware of the ios swift has a Contacts Framework where I can fetch contacts, but I cannot find any method to fetch all the contacts together where I can access each of the contacts from that array. All methods for fetching contacts seems to require some sort of conditions. Is there any method where I can get all the contacts together?

Thanks

the_naive
  • 2,936
  • 6
  • 39
  • 68
  • 1
    see this question: http://stackoverflow.com/questions/3747844/get-a-list-of-all-contacts-on-ios – Ollie Nov 28 '15 at 16:28
  • @Ollie OP is looking for solution for "Contacts Framework" whereas the link you posted is still ABAddressBook, which I reckon is not helpful. – Christopher May 09 '16 at 05:13
  • 1
    @Christopher many of the solutions on that question actually use the Contacts Framework. The second answer gives a detailed explanation. – Ollie May 09 '16 at 08:54

8 Answers8

46

Swift 4 and 5. I have create class PhoneContacts. Please add NSContactsUsageDescription key to your info.plist file

 import Foundation
 import ContactsUI

class PhoneContacts {

    class func getContacts(filter: ContactsFilter = .none) -> [CNContact] { //  ContactsFilter is Enum find it below

        let contactStore = CNContactStore()
        let keysToFetch = [
            CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
            CNContactPhoneNumbersKey,
            CNContactEmailAddressesKey,
            CNContactThumbnailImageDataKey] as [Any]

        var allContainers: [CNContainer] = []
        do {
            allContainers = try contactStore.containers(matching: nil)
        } catch {
            print("Error fetching containers")
        }

        var results: [CNContact] = []

        for container in allContainers {
            let fetchPredicate = CNContact.predicateForContactsInContainer(withIdentifier: container.identifier)

            do {
                let containerResults = try contactStore.unifiedContacts(matching: fetchPredicate, keysToFetch: keysToFetch as! [CNKeyDescriptor])
                results.append(contentsOf: containerResults)
            } catch {
                print("Error fetching containers")
            }
        }
        return results
    }
}

The calling to above method in another class

import ContactsUI
func phoneNumberWithContryCode() -> [String] {

    let contacts = PhoneContacts.getContacts() // here calling the getContacts methods
    var arrPhoneNumbers = [String]()
    for contact in contacts {
        for ContctNumVar: CNLabeledValue in contact.phoneNumbers {
            if let fulMobNumVar  = ContctNumVar.value as? CNPhoneNumber {
                //let countryCode = fulMobNumVar.value(forKey: "countryCode") get country code
                   if let MccNamVar = fulMobNumVar.value(forKey: "digits") as? String {
                        arrPhoneNumbers.append(MccNamVar)
                }
            }
        }
    }
    return arrPhoneNumbers // here array has all contact numbers.
}

Now, Get email and phone of contacts

    enum ContactsFilter {
        case none
        case mail
        case message
    }

    var phoneContacts = [PhoneContact]() // array of PhoneContact(It is model find it below) 
    var filter: ContactsFilter = .none

    self.loadContacts(filter: filter) // Calling loadContacts methods

       fileprivate func loadContacts(filter: ContactsFilter) {
            phoneContacts.removeAll()
            var allContacts = [PhoneContact]()
            for contact in PhoneContacts.getContacts(filter: filter) {
                allContacts.append(PhoneContact(contact: contact))
            }

            var filterdArray = [PhoneContact]()
            if self.filter == .mail {
                filterdArray = allContacts.filter({ $0.email.count > 0 }) // getting all email 
            } else if self.filter == .message {
                filterdArray = allContacts.filter({ $0.phoneNumber.count > 0 })
            } else {
                filterdArray = allContacts
            }
            phoneContacts.append(contentsOf: filterdArray)

  for contact in phoneContacts {
      print("Name -> \(contact.name)")
      print("Email -> \(contact.email)")
      print("Phone Number -> \(contact.phoneNumber)")
    }
    let arrayCode  = self.phoneNumberWithContryCode()
    for codes in arrayCode {
      print(codes)
    }
     DispatchQueue.main.async {
       self.tableView.reloadData() // update your tableView having phoneContacts array
              }
            }
        }

PhoneContact Model Class

import Foundation
import ContactsUI

class PhoneContact: NSObject {

    var name: String?
    var avatarData: Data?
    var phoneNumber: [String] = [String]()
    var email: [String] = [String]()
    var isSelected: Bool = false
    var isInvited = false

    init(contact: CNContact) {
        name        = contact.givenName + " " + contact.familyName
        avatarData  = contact.thumbnailImageData
        for phone in contact.phoneNumbers {
            phoneNumber.append(phone.value.stringValue)
        }
        for mail in contact.emailAddresses {
            email.append(mail.value as String)
        }
    }

    override init() {
        super.init()
    }
}
Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
  • I am having "Conditional cast from 'CNPhoneNumber' to 'CNPhoneNumber' always succeeds" for "if let fulMobNumVar = ContctNumVar.value as? CNPhoneNumber" – Utku Dalmaz Sep 28 '18 at 08:40
45

Many answers to Contact Framework questions suggest iterating over various containers (accounts). However, Apple's documentation describes a "Unified Contact" as

Contacts in different accounts that represent the same person may be automatically linked together. Linked contacts are displayed in OS X and iOS apps as unified contacts. A unified contact is an in-memory, temporary view of the set of linked contacts that are merged into one contact.

By default the Contacts framework returns unified contacts. Each fetched unified contact (CNContact) object has its own unique identifier that is different from any individual contact’s identifier in the set of linked contacts. A refetch of a unified contact should be done with its identifier. Source

So simplest way to fetch a list of (partial, based on keys) contacts in a single array, would be the following:

      var contacts = [CNContact]()
    let keys = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName)]
    let request = CNContactFetchRequest(keysToFetch: keys)
    
    let contactStore = CNContactStore()
    do {
        try contactStore.enumerateContacts(with: request) {
            (contact, stop) in
            // Array containing all unified contacts from everywhere
            contacts.append(contact)
        }
    }
    catch {
        print("unable to fetch contacts")
    }
Shanu Singh
  • 365
  • 3
  • 16
Ri_
  • 662
  • 8
  • 13
  • 2
    very helpful. if i want to fetch name and number , and if user has multiple number how to solve that situation – Bora Sep 05 '16 at 09:10
  • CNContact returns array of CNPhoneNumbers. You can simply take the first number in the array, or iterate over the list and parse the phone number's CNLabeledValues to find the one you want, e.g. "work" or "mobile" – Ri_ Sep 06 '16 at 08:59
  • where is `self.contactStore` comming from? – softmarshmallow Jan 30 '20 at 05:12
  • 2
    @uzu it should be defined as `contactStore = CNContactStore()` and `self.contactStore.enumerateContactsWithFetchRequest(request)` should be changed to `self.contactStore.enumerateContacts(with: request)`. – Wimukthi Rajapaksha Mar 06 '20 at 06:39
29

Update for Swift 4

let contactStore = CNContactStore()
var contacts = [CNContact]()
let keys = [
        CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
                CNContactPhoneNumbersKey,
                CNContactEmailAddressesKey
        ] as [Any]
let request = CNContactFetchRequest(keysToFetch: keys as! [CNKeyDescriptor])
do {
    try contactStore.enumerateContacts(with: request){
            (contact, stop) in
        // Array containing all unified contacts from everywhere
        contacts.append(contact)
        for phoneNumber in contact.phoneNumbers {
            if let number = phoneNumber.value as? CNPhoneNumber, let label = phoneNumber.label {
                let localizedLabel = CNLabeledValue<CNPhoneNumber>.localizedString(forLabel: label)
                print("\(contact.givenName) \(contact.familyName) tel:\(localizedLabel) -- \(number.stringValue), email: \(contact.emailAddresses)")
            }
        }
    }
    print(contacts)
} catch {
    print("unable to fetch contacts")
}
Shan Ye
  • 2,622
  • 19
  • 21
  • 2
    Before adding this code add permission request in `.Plist` with key : `Privacy - Contacts Usage Description` and value : `Application wants to access your contacts list.` ... – Wahab Khan Jadon Mar 04 '20 at 11:29
7
    // You may add more "keys" to fetch referred to official documentation
    let keysToFetch = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName)]

    // The container means 
    // that the source the contacts from, such as Exchange and iCloud
    var allContainers: [CNContainer] = []
    do {
        allContainers = try store.containersMatchingPredicate(nil)
    } catch {
        print("Error fetching containers")
    }

    var contacts: [CNContact] = []

    // Loop the containers
    for container in allContainers {
        let fetchPredicate = CNContact.predicateForContactsInContainerWithIdentifier(container.identifier)

        do {
            let containerResults = try store.unifiedContactsMatchingPredicate(fetchPredicate, keysToFetch: keysToFetch)
            // Put them into "contacts"
            contacts.appendContentsOf(containerResults)
        } catch {
            print("Error fetching results for container")
        }
    }
Christopher
  • 731
  • 6
  • 24
7

A Swift 4.0 implementation to pull in all of the contacts.

let contactStore = CNContactStore()
var contacts = [CNContact]()
let keys = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName)]
let request = CNContactFetchRequest(keysToFetch: keys)

do {
    try contactStore.enumerateContacts(with: request) { (contact, stop) in
        contacts.append(contact)
    }
} catch {
    print(error.localizedDescription)
}

This creates a local property to store the contacts, which are then populated via the enumeration against contactStore.

CodeBender
  • 35,668
  • 12
  • 125
  • 132
3

Please see my answer it is based on answers above with certain improvements, just do the following

In your pod file

source 'https://github.com/CocoaPods/Specs.git'
pod 'PhoneNumberKit', '~> 2.6'

then run pod install

then in your ViewController File

import Contacts
import PhoneNumberKit
import UIKit

override func viewDidLoad() {
    super.viewDidLoad()

    let contactStore = CNContactStore()
    var contacts = [CNContact]()
    let keys = [
        CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
        CNContactPhoneNumbersKey,
        CNContactEmailAddressesKey,
    ] as [Any]
    let request = CNContactFetchRequest(keysToFetch: keys as! [CNKeyDescriptor])
    do {
        try contactStore.enumerateContacts(with: request) {
            contact, _ in
            // Array containing all unified contacts from everywhere
            contacts.append(contact)
            for phoneNumber in contact.phoneNumbers {
                if let number = phoneNumber.value as? CNPhoneNumber, let label = phoneNumber.label {
                    let localizedLabel = CNLabeledValue<CNPhoneNumber>.localizedString(forLabel: label)

                    // Get The Name
                    let name = contact.givenName + " " + contact.familyName
                    print(name)

                    // Get The Mobile Number
                    var mobile = number.stringValue
                    mobile = mobile.replacingOccurrences(of: " ", with: "")

                    // Parse The Mobile Number
                    let phoneNumberKit = PhoneNumberKit()

                    do {
                        let phoneNumberCustomDefaultRegion = try phoneNumberKit.parse(mobile, withRegion: "IN", ignoreType: true)
                        let countryCode = String(phoneNumberCustomDefaultRegion.countryCode)
                        let mobile = String(phoneNumberCustomDefaultRegion.nationalNumber)
                        let finalMobile = "+" + countryCode + mobile
                        print(finalMobile)
                    } catch {
                        print("Generic parser error")
                    }

                    // Get The Email
                    var email: String
                    for mail in contact.emailAddresses {
                        email = mail.value as String
                        print(email)
                    }
                }
            }
        }

    } catch {
        print("unable to fetch contacts")
    }
}
DragonFire
  • 3,722
  • 2
  • 38
  • 51
  • PhoneNumberKit is very expensive in performance especially when you have more than 200 contacts – Usman Shahid Aug 15 '19 at 13:19
  • 1
    @UsmanShahid do you have any other suggestion which works better.. thnks.. – DragonFire Aug 16 '19 at 01:29
  • 1
    yes use this library, it is very fast and accurate (can be used both in swift & objective-c) https://github.com/iziz/libPhoneNumber-iOS – Usman Shahid Aug 16 '19 at 08:28
  • 2
    Your code performance is very low because of this line `let phoneNumberKit = PhoneNumberKit()` you run this line for all contacts, put it outside of `for` loop. – Hamed Nov 13 '19 at 12:21
  • What's the benefit of using PhoneNumberKit? Seems like the default Contacts library is fairly straightforward? ‍♂️ – Zorayr Aug 31 '20 at 21:43
3

Please Try below function, it helps you (Swift 4)

import UIKit
import Contacts
import ContactsUI

override func viewDidLoad() {
    super.viewDidLoad()

    // `contacts` Contains all details of Phone Contacts
    let contacts = self.getContactFromCNContact() 
    for contact in contacts {

        print(contact.middleName)
        print(contact.familyName)
        print(contact.givenName)
    }
}

func getContactFromCNContact() -> [CNContact] {

    let contactStore = CNContactStore()
    let keysToFetch = [
        CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
        CNContactGivenNameKey,
        CNContactMiddleNameKey,
        CNContactFamilyNameKey,
        CNContactEmailAddressesKey,
        ] as [Any]

    //Get all the containers
    var allContainers: [CNContainer] = []
    do {
        allContainers = try contactStore.containers(matching: 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.predicateForContactsInContainer(withIdentifier: container.identifier)

        do {
            let containerResults = try contactStore.unifiedContacts(matching: fetchPredicate, keysToFetch: keysToFetch as! [CNKeyDescriptor])
            results.append(contentsOf: containerResults)

        } catch {
            print("Error fetching results for container")
        }
    }

    return results
}
Rohit Makwana
  • 4,337
  • 1
  • 21
  • 29
3

Complex solution:

var contacts = [CNContact]()

let keys: [Any] = [
    CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
    CNContactImageDataKey,
    CNContactEmailAddressesKey,
    CNContactPhoneNumbersKey,
    CNContactJobTitleKey,
    CNContactBirthdayKey,
    CNContactPostalAddressesKey
]

let request = CNContactFetchRequest(keysToFetch: keys as! [CNKeyDescriptor])
let contactStore = CNContactStore()

try? contactStore.enumerateContacts(with: request, usingBlock: { contact, _ in
    contacts.append(contact)
})

Don't forget about Info.plist:

NSContactsUsageDescription
Arthur Stepanov
  • 511
  • 7
  • 4