Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing iOS Address Book with Swift: array count of zero

I am trying to write a simple method to ask a user for access to their address book and then print out the name of each person in the address book. I've seen a number of tutorials explaining how to do this in objective-C, but am having a hard time converting them to swift.

Here's what I've done so far. The below block runs in my viewDidLoad() method and checks to see whether the user has authorized access to the address book or not, if they have not authorized access yet, the first if-statement will ask for 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()         } 

Once I know the user has granted access, I run the getContactNames() method which is below. After much back and forth, I was finally able to get this to compile by adding the takeRetainedValue() method in order to convert the array returned by ABAddressBookCopyArrayOfAllPeople from an unmanaged array to a managed array, this then allows me to convert the CFArrayRef to an NSArray.

The issue I'm running into is that the contactList array ends up having a count of 0 and the for loop therefore gets skipped. In my simulator, the address book has 6 or 7 records, so I would expect the array to be of that length. 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)")         }     } 

One additional 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 seems that this function returning -1 could be related to permission not being granted, but I definitely have asked for permission in the code above (and granted it when I run the app in the simulator)

like image 226
user1031648 Avatar asked Jul 15 '14 07:07

user1031648


2 Answers

This is now all much simpler. The chief thing to watch out for is that if you create an ABAddressBook without authorization, you get an evil address book - it isn't nil but it isn't good for anything either. Here's how I currently recommend that you set up 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 cycle through all persons and print out 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())     } } 
like image 122
matt Avatar answered Sep 29 '22 09:09

matt


There seems to be a bug either with the compiler or the framework where ABAddressBookRef is declared a typealias of AnyObject, but it needs to be NSObject in order to unwrap it from the Unmanaged<ABAddressBookRef>! returned by ABAddressBookCreateWithOptions. A workaround is to convert it to and from an opaque C pointer. The following code works, but it should probably be doing a lot more error checking (and there is also probably a better way of working around this issue):

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)")     } } 
like image 34
Wes Campaigne Avatar answered Sep 29 '22 10:09

Wes Campaigne