swiftaddressbookabaddressbookpeoplepickeraddressbookui

How to select a contact with ABPeoplePickerNavigationController in Swift?


I have added the ABPeoplePickerNavigationController into my first view controller. I want that when I select a contact show the info to show in other view controller, but I'm trying use my code and this not show never when I click in a contact. This only open the contact into native app ABPeoplePickerNavigationController.

var people = ABPeoplePickerNavigationController()
var addressBook: ABAddressBookRef?

func extractABAddressBookRef(abRef: Unmanaged<ABAddressBookRef>!) -> ABAddressBookRef? {
    if let ab = abRef {
        self.view.addSubview(people.view)
        return Unmanaged<NSObject>.fromOpaque(ab.toOpaque()).takeUnretainedValue()
    }
    return nil
}

I tried this function

func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!,didSelectPerson person: ABRecordRef!) {

    var unmanagedEmails = ABRecordCopyValue(people, kABPersonEmailProperty)
    let emailObj: ABMultiValueRef = Unmanaged.fromOpaque(unmanagedEmails.toOpaque()).takeUnretainedValue() as NSObject as ABMultiValueRef

    var index = 0 as CFIndex

    var unmanagedEmail = ABMultiValueCopyValueAtIndex(emailObj, index)
    var emailAddress:String = Unmanaged.fromOpaque(unmanagedEmail.toOpaque()).takeUnretainedValue() as NSObject as String

    println(emailAddress)      
}

Thanks!


Solution

  • A couple of thoughts:

    1. Have you set the peoplePickerDelegate property of the people picker controller? If you don't do that, it won't know to try to call these methods in your class. Thus:

      people.peoplePickerDelegate = self
      presentViewController(people, animated: true, completion: nil)
      
    2. Your example method is referencing people when you call ABRecordCopyValue. That's your picker controller. I assume you meant to reference person, the ABRecordRef! that was passed as a parameter.

      You might also want to make sure you actually have an email address before trying to access it. You can use ABMultiValueGetCount.

      I also think you can also eliminate that fromOpaque/toOpaque dance.

      This yields:

      func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController, didSelectPerson person: ABRecord) {
          let emails: ABMultiValueRef = ABRecordCopyValue(person, kABPersonEmailProperty).takeRetainedValue()
          if ABMultiValueGetCount(emails) > 0 {
              let index = 0 as CFIndex
              let emailAddress = ABMultiValueCopyValueAtIndex(emails, index).takeRetainedValue() as! String
      
              print(emailAddress)
          } else {
              print("No email address")
          }
      }
      
    3. If you need to support iOS 7, too, use:

      func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController, shouldContinueAfterSelectingPerson person: ABRecord, property: ABPropertyID, identifier: ABMultiValueIdentifier) -> Bool {
          let multiValue: ABMultiValueRef = ABRecordCopyValue(person, property).takeRetainedValue()
          let index = ABMultiValueGetIndexForIdentifier(multiValue, identifier)
          let email = ABMultiValueCopyValueAtIndex(multiValue, index).takeRetainedValue() as! String
      
          print("email = \(email)")
      
          peoplePicker.dismissViewControllerAnimated(true, completion: nil)
      
          return false
      }
      
    4. You might, though, rather than assuming the user only wanted the first email address, instead, let them click through and pick one of the possible multiple email addresses the contact had. So, first, you might want to eliminate some of the "noise", by telling the picker that you only want to see email addresses:

      people.peoplePickerDelegate = self
      people.displayedProperties = [NSNumber(int: kABPersonEmailProperty)]
      presentViewController(people, animated: true, completion: nil)
      

      And then, remove the prior method we've been discussing, and instead implement:

      func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecordRef!, property: ABPropertyID, identifier: ABMultiValueIdentifier) {
          let multiValue: ABMultiValueRef = ABRecordCopyValue(person, property).takeRetainedValue()
          let index = ABMultiValueGetIndexForIdentifier(multiValue, identifier)
          let email = ABMultiValueCopyValueAtIndex(multiValue, index).takeRetainedValue() as String
      
          println("email = \(email)")
      }
      

      And to support iOS 7,0, too, you'd add:

      func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController, shouldContinueAfterSelectingPerson person: ABRecord, property: ABPropertyID, identifier: ABMultiValueIdentifier) -> Bool {
          let multiValue: ABMultiValueRef = ABRecordCopyValue(person, property).takeRetainedValue()
          let index = ABMultiValueGetIndexForIdentifier(multiValue, identifier)
          let email = ABMultiValueCopyValueAtIndex(multiValue, index).takeRetainedValue() as! String
      
          print("email = \(email)")
      
          peoplePicker.dismissViewControllerAnimated(true, completion: nil)
      
          return false
      }
      
    5. By the way, iOS 8 offers a feature to control whether a contact is enabled or not. Since you're supporting iOS 7 and 8, you'd want to employ that conditionally, such as:

      if people.respondsToSelector(Selector("predicateForEnablingPerson")) {
          people.predicateForEnablingPerson = NSPredicate(format: "emailAddresses.@count > 0")
      }
      

      This gives the user visual indication whether there is even an email address for the individual, and prevents them from selecting entry without email address.

    Obviously, if using iOS 9 and later, you should retire all of this and use the ContactsUI framework, which simplifies the code further.