Access to the iOS address book using Swift: number of arrays to zero

I am trying to write a simple method to ask a user to access their address book, and then print the name of each person in the address book. I saw several tutorials explaining how to do this in objective-C, but it's hard for me to convert them to swift.

Here is what I have done so far. The following block works in my viewDidLoad () method and checks if the user has authorized access to the address book or not, if they do not already have authorized access, the first if request requests access. This section works as expected.

var emptyDictionary: CFDictionaryRef? var addressBook: ABAddressBookRef? if (ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.NotDetermined) { println("requesting access...") addressBook = !ABAddressBookCreateWithOptions(emptyDictionary,nil) ABAddressBookRequestAccessWithCompletion(addressBook,{success, error in if success { self.getContactNames(); } else { println("error") } }) } } else if (ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.Denied || ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.Restricted) { println("access denied") } else if (ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.Authorized) { println("access granted") getContactNames() } 

As soon as I know that the user has granted access, I launched the getContactNames () method, which is below. After going back and forth a lot, I finally was able to get this to compile by adding the takeRetainedValue () method to convert the array returned by ABAddressBookCopyArrayOfAllPeople from an unmanaged array to a managed array, then this allows me to convert CFArrayRef to NSArray.

The problem I am facing is that the contactList array ends up having a number 0, and therefore the for loop is therefore skipped. In my sim, the address book has 6 or 7 entries, so I expect the array to be that long. Any ideas?

 func getContactNames() { addressBook = !ABAddressBookCreateWithOptions(emptyDictionary,nil) var contactList: NSArray = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() println("records in the array \(contactList.count)") // returns 0 for record:ABRecordRef in contactList { var contactPerson: ABRecordRef = record var contactName: String = ABRecordCopyCompositeName(contactPerson).takeRetainedValue() println ("contactName \(contactName)") } } 

Another point - if I use the ABAddressBookGetPersonCount method, it returns -1.

  var count: CFIndex = ABAddressBookGetPersonCount(addressBook); println("records in the array \(count)") // returns -1 

Based on this link, ABAddressBookGetPersonCount returns -1 in iOS , it looks like this function returning -1 could be related to granting permission, but I definitely asked for permission in the above code (and provided it when the application started in the simulator)

+45
ios swift abaddressbook
Jul 15 '14 at 7:46
source share
9 answers

Now it is much easier. The main thing you need to pay attention to is that if you create an ABAddressBook without authorization, you will receive an evil address book - this is not zero, but it doesn’t work for anything. Here, as I now recommend you set the authorization status and request authorization, if necessary:

 var adbk : ABAddressBook! func createAddressBook() -> Bool { if self.adbk != nil { return true } var err : Unmanaged<CFError>? = nil let adbk : ABAddressBook? = ABAddressBookCreateWithOptions(nil, &err).takeRetainedValue() if adbk == nil { println(err) self.adbk = nil return false } self.adbk = adbk return true } func determineStatus() -> Bool { let status = ABAddressBookGetAuthorizationStatus() switch status { case .Authorized: return self.createAddressBook() case .NotDetermined: var ok = false ABAddressBookRequestAccessWithCompletion(nil) { (granted:Bool, err:CFError!) in dispatch_async(dispatch_get_main_queue()) { if granted { ok = self.createAddressBook() } } } if ok == true { return true } self.adbk = nil return false case .Restricted: self.adbk = nil return false case .Denied: self.adbk = nil return false } } 

And here's how to get through all the people and print their names:

 func getContactNames() { if !self.determineStatus() { println("not authorized") return } let people = ABAddressBookCopyArrayOfAllPeople(adbk).takeRetainedValue() as NSArray as [ABRecord] for person in people { println(ABRecordCopyCompositeName(person).takeRetainedValue()) } } 
+27
Aug 20 '14 at 16:44
source share

It seems that there is an error either with the compiler or with the framework where ABAddressBookRef declared, but to deploy it from Unmanaged<ABAddressBookRef>! returned by ABAddressBookCreateWithOptions . The workaround is to convert it to an opaque pointer C. The following code works, but it should probably do a lot more error checking (and there is probably also a better way around this problem):

 var addressBook: ABAddressBookRef? func extractABAddressBookRef(abRef: Unmanaged<ABAddressBookRef>!) -> ABAddressBookRef? { if let ab = abRef { return Unmanaged<NSObject>.fromOpaque(ab.toOpaque()).takeUnretainedValue() } return nil } func test() { if (ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.NotDetermined) { println("requesting access...") var errorRef: Unmanaged<CFError>? = nil addressBook = extractABAddressBookRef(ABAddressBookCreateWithOptions(nil, &errorRef)) ABAddressBookRequestAccessWithCompletion(addressBook, { success, error in if success { self.getContactNames() } else { println("error") } }) } else if (ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.Denied || ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.Restricted) { println("access denied") } else if (ABAddressBookGetAuthorizationStatus() == ABAuthorizationStatus.Authorized) { println("access granted") self.getContactNames() } } func getContactNames() { var errorRef: Unmanaged<CFError>? addressBook = extractABAddressBookRef(ABAddressBookCreateWithOptions(nil, &errorRef)) var contactList: NSArray = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() println("records in the array \(contactList.count)") for record:ABRecordRef in contactList { var contactPerson: ABRecordRef = record var contactName: String = ABRecordCopyCompositeName(contactPerson).takeRetainedValue() as NSString println ("contactName \(contactName)") } } 
+15
Jul 24 '14 at 4:45
source share

For those who are looking for a complete working solution, here's how to print only contact names by changing the code above. Call getAddressBookNames() to access the address book, for example. in the viewDidLoad() method.

 func getAddressBookNames() { let authorizationStatus = ABAddressBookGetAuthorizationStatus() if (authorizationStatus == ABAuthorizationStatus.NotDetermined) { NSLog("requesting access...") var emptyDictionary: CFDictionaryRef? var addressBook = !ABAddressBookCreateWithOptions(emptyDictionary, nil) ABAddressBookRequestAccessWithCompletion(addressBook,{success, error in if success { self.getContactNames(); } else { NSLog("unable to request access") } }) } else if (authorizationStatus == ABAuthorizationStatus.Denied || authorizationStatus == ABAuthorizationStatus.Restricted) { NSLog("access denied") } else if (authorizationStatus == ABAuthorizationStatus.Authorized) { NSLog("access granted") getContactNames() } } func getContactNames() { var errorRef: Unmanaged<CFError>? var addressBook: ABAddressBookRef? = extractABAddressBookRef(ABAddressBookCreateWithOptions(nil, &errorRef)) var contactList: NSArray = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() println("number of contacts: \(contactList.count)") for record:ABRecordRef in contactList { var contactName: String = ABRecordCopyCompositeName(record).takeRetainedValue() as NSString NSLog("contactName: \(contactName)") } } func extractABAddressBookRef(abRef: Unmanaged<ABAddressBookRef>!) -> ABAddressBookRef? { if let ab = abRef { return Unmanaged<NSObject>.fromOpaque(ab.toOpaque()).takeUnretainedValue() } return nil } 

And here is the complete code for accessing contact names and email messages - this is done using the helper methods defined in some other answers.

 func getAddressBookNames() { let authorizationStatus = ABAddressBookGetAuthorizationStatus() if (authorizationStatus == ABAuthorizationStatus.NotDetermined) { NSLog("requesting access...") var emptyDictionary: CFDictionaryRef? var addressBook = !ABAddressBookCreateWithOptions(emptyDictionary, nil) ABAddressBookRequestAccessWithCompletion(addressBook,{success, error in if success { self.processContactNames(); } else { NSLog("unable to request access") } }) } else if (authorizationStatus == ABAuthorizationStatus.Denied || authorizationStatus == ABAuthorizationStatus.Restricted) { NSLog("access denied") } else if (authorizationStatus == ABAuthorizationStatus.Authorized) { NSLog("access granted") processContactNames() } } func processContactNames() { var errorRef: Unmanaged<CFError>? var addressBook: ABAddressBookRef? = extractABAddressBookRef(ABAddressBookCreateWithOptions(nil, &errorRef)) var contactList: NSArray = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() println("records in the array \(contactList.count)") for record:ABRecordRef in contactList { processAddressbookRecord(record) } } func processAddressbookRecord(addressBookRecord: ABRecordRef) { var contactName: String = ABRecordCopyCompositeName(addressBookRecord).takeRetainedValue() as NSString NSLog("contactName: \(contactName)") processEmail(addressBookRecord) } func processEmail(addressBookRecord: ABRecordRef) { let emailArray:ABMultiValueRef = extractABEmailRef(ABRecordCopyValue(addressBookRecord, kABPersonEmailProperty))! for (var j = 0; j < ABMultiValueGetCount(emailArray); ++j) { var emailAdd = ABMultiValueCopyValueAtIndex(emailArray, j) var myString = extractABEmailAddress(emailAdd) NSLog("email: \(myString!)") } } func extractABAddressBookRef(abRef: Unmanaged<ABAddressBookRef>!) -> ABAddressBookRef? { if let ab = abRef { return Unmanaged<NSObject>.fromOpaque(ab.toOpaque()).takeUnretainedValue() } return nil } func extractABEmailRef (abEmailRef: Unmanaged<ABMultiValueRef>!) -> ABMultiValueRef? { if let ab = abEmailRef { return Unmanaged<NSObject>.fromOpaque(ab.toOpaque()).takeUnretainedValue() } return nil } func extractABEmailAddress (abEmailAddress: Unmanaged<AnyObject>!) -> String? { if let ab = abEmailAddress { return Unmanaged.fromOpaque(abEmailAddress.toOpaque()).takeUnretainedValue() as CFStringRef } return nil } 
+8
Jul 29 '14 at 9:42 on
source share

If someone is trying to get the email addresses of contacts, I find that I need to create two additional methods, similar to the new one shown by Wes.

Here's the updated version of the getContactNames () function:

  func getContactNames() { var errorRef: Unmanaged<CFError>? addressBook = extractABAddressBookRef(ABAddressBookCreateWithOptions(nil, &errorRef)) var contactList: NSArray = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() println("records in the array \(contactList.count)") for record:ABRecordRef in contactList { var contactPerson: ABRecordRef = record var contactName: String = ABRecordCopyCompositeName(contactPerson).takeRetainedValue() as NSString println ("contactName \(contactName)") var emailArray:ABMultiValueRef = extractABEmailRef(ABRecordCopyValue(contactPerson, kABPersonEmailProperty))! for (var j = 0; j < ABMultiValueGetCount(emailArray); ++j) { var emailAdd = ABMultiValueCopyValueAtIndex(emailArray, j) var myString = extractABEmailAddress(emailAdd) println("email: \(myString)") } } } 

And here are two additional features that I created:

  func extractABEmailRef (abEmailRef: Unmanaged<ABMultiValueRef>!) -> ABMultiValueRef? { if let ab = abEmailRef { return Unmanaged<NSObject>.fromOpaque(ab.toOpaque()).takeUnretainedValue() } return nil } func extractABEmailAddress (abEmailAddress: Unmanaged<AnyObject>!) -> String? { if let ab = abEmailAddress { return Unmanaged.fromOpaque(abEmailAddress.toOpaque()).takeUnretainedValue() as CFStringRef } return nil } 

Thanks again to Wes for helping me with my original question, which helped me sort this out.

+4
Jul 25 '14 at 2:30
source share

If you need an email in addition to the matte answer:

 func getContacts() { if !self.determineStatus() { println("not authorized") } let people = ABAddressBookCopyArrayOfAllPeople(adbk).takeRetainedValue() as NSArray as [ABRecord] for person in people { // Name let name = ABRecordCopyCompositeName(person).takeRetainedValue() // Email let emails: ABMultiValueRef = ABRecordCopyValue(person, kABPersonEmailProperty).takeRetainedValue() for (var i = 0; i < ABMultiValueGetCount(emails); i++) { let email: String = ABMultiValueCopyValueAtIndex(emails, i).takeRetainedValue() as String println("email=\(email)") } } } 
+3
Mar 09 '15 at 6:02
source share

This is an old question, but another answer may still be useful: I took the approach to solving problems with the address book: https://github.com/SocialbitGmbH/SwiftAddressBook

I must mention that there are many wrappers for ABAddressBook that can help you avoid problems like the one you asked for in full. Thus, I find the link “answer” to the problem (although it does not answer how to fix your code)

+2
Dec 16 '14 at 7:58
source share

To add to the information here, this is my solution, compiled from different places (there is a good Apple site that really describes this, the documents I found basically provide almost nothing, except that there are args / members names):

  let addrBook = ABAddressBookCreateWithOptions(nil,nil).takeRetainedValue() let contacts = ABAddressBookCopyArrayOfAllPeople(addrBook).takeRetainedValue() as NSArray as [ABRecordRef] for contact in contacts { let fname = ABRecordCopyValue(contact, kABPersonFirstNameProperty).takeRetainedValue() as! NSString let lname = ABRecordCopyValue(contact, kABPersonLastNameProperty).takeRetainedValue() as! NSString let name = String(fname) + " " + String(lname) var image:UIImage? = nil if ABPersonHasImageData(contact) { image = UIImage(data: ABPersonCopyImageDataWithFormat(contact, kABPersonImageFormatThumbnail).takeRetainedValue() as NSData) } if let emailRefs: ABMultiValueRef = ABRecordCopyValue(contact, kABPersonEmailProperty).takeRetainedValue() { let nEmailsForContact = ABMultiValueGetCount(emailRefs) if nEmailsForContact > 0 { if let emailArray: NSArray = ABMultiValueCopyArrayOfAllValues(emailRefs).takeRetainedValue() as NSArray { for emailW in emailArray { let email = String(emailW) if email.containsString("@") { let c: EmailContact = EmailContact(n: name, e: email, a: false, i: image) mEmailContacts.append(c) } } } } } } 

Oddly enough, you need to check if there is an image if you want to access it; and you should check that there is at least one email for the contact before trying to retrieve them (why doesn't it just return an empty list instead?).

The EmailContact class is what I did to capture the results, but not shown, but the code snippet shows how to extract information for the current version of swift / ios.

In addition, I note that website settings seem to appear in EmailArray for contacts as well as actual emails. Right now, I'm just checking the @ sign to determine if this letter is valid, but is there a better or “official” way to do this?

Finally, hopefully this is a memory leak.

Oh, of course, this is done after obtaining permission, if you do not know how to do it, then this site is good: http://www.raywenderlich.com/63885/address-book-tutorial-in-ios

+1
Jan 03 '15 at 21:40
source share

The other answers provided here were useful and guided by this answer, but had errors and / or were not updated for Swift 3. The following class provides a number of simplifications and security improvements.

Using just to call AddressBookService.getContactNames

There are good reasons, you still need to use the ABAddressBook structure, since CNContact does not provide some key data, including creation and change dates, for example. Warnings about legacy methods are somewhat distracting when working with code, so this code suppresses warnings that ABAddressBook methods were deprecated from iOS 9 onwards, instead providing only one warning for this effect when you call the class below.

 // // AddressBookService.swift // import AddressBook @available(iOS, deprecated: 9.0) class AddressBookService: NSObject { class func getContactNames() { let authorizationStatus = ABAddressBookGetAuthorizationStatus() switch authorizationStatus { case .authorized: retrieveContactNames() break case .notDetermined: print("Requesting Address Book access...") let addressBook = AddressBookService.addressBook ABAddressBookRequestAccessWithCompletion(addressBook, {success, error in if success { print("Address book access granted") retrieveContactNames() } else { print("Unable to obtain Address Book access.") } }) break case .restricted, .denied: print("Address book access denied") break } } private class func retrieveContactNames() { let addressBook = ABAddressBookCreate().takeRetainedValue() let contactList = ABAddressBookCopyArrayOfAllPeople(addressBook).takeRetainedValue() as NSArray as [ABRecord] for (index, record) in contactList.enumerated() { if let contactName = ABRecordCopyCompositeName(record)?.takeRetainedValue() as String? { print("Contact \(index): \(contactName))") } } } } 
+1
Aug 04 '17 at 3:42 on
source share

Not the best solution, but until I find this job

 let records = ABAddressBookCopyArrayOfAllPeople(self.addressBook).takeRetainedValue() as NSArray as [ABRecord] sleep(2) println(records.count); 
0
Jan 08 '15 at 16:04
source share



All Articles