1

I have the following code which copies data from the iPhone's contacts into an NSMutableDictionary. I'm using optional binding as discussed in this answer.

The firstName and lastName fields copy properly (as well as phone and email fields not shown here), but I have never been able to access the kABPersonNoteProperty, nor have I seen the println statement in the log. I've tried to copy the note as both String and NSString without success. Any ideas what could be causing this problem?

var contactInfoDict:NSMutableDictionary!

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

    self.contactInfoDict = ["firstName":"", "lastName":"", "notes":""]

    if let firstName:String = ABRecordCopyValue(person, kABPersonFirstNameProperty)?.takeRetainedValue() as? String {
        contactInfoDict.setObject(firstName, forKey: "firstName")
        println(firstName)
    }

    if let lastName:String = ABRecordCopyValue(person, kABPersonLastNameProperty)?.takeRetainedValue() as? String {
        contactInfoDict.setObject(lastName, forKey: "lastName")
        println(lastName)
    }

    if let notes:String = ABRecordCopyValue(person, kABPersonNoteProperty)?.takeRetainedValue() as? String {
        contactInfoDict.setObject(notes, forKey: "notes")
        println("Note: \(notes)")
    }

}

Edit Here is the full class:

import UIKit
import AddressBookUI

class ContactsVC: UIViewController, ABPeoplePickerNavigationControllerDelegate {

@IBOutlet weak var done_Btn: UIBarButtonItem!
@IBAction func dismissContacts(sender: UIBarButtonItem) {
    self.dismissViewControllerAnimated(true, completion: nil)
    println("ContactsVC dismissed")
}

@IBOutlet weak var viewContainer: UIView!

var object:PFObject = PFObject(className: "Contact")
let personPicker: ABPeoplePickerNavigationController
var contactInfoDict:NSMutableDictionary!

required init(coder aDecoder: NSCoder) {
    personPicker = ABPeoplePickerNavigationController()
    super.init(coder: aDecoder)
    personPicker.peoplePickerDelegate = self
}

override func viewDidLoad() {
    super.viewDidLoad()

    done_Btn.setTitleTextAttributes([NSFontAttributeName: UIFont(name: "Avenir Next Medium", size: 16)!], forState: UIControlState.Normal)

}


@IBAction func addContact() {

    let actionSheetController: UIAlertController = UIAlertController(title: nil, message: "Would you like to select a contact from your iPhone or create a new contact directly in the app?", preferredStyle: .ActionSheet)

    let selectContactAction: UIAlertAction = UIAlertAction(title: "Select from iPhone", style: .Default) { action -> Void in
        self.performPickPerson(UIAlertAction)
    }
    actionSheetController.addAction(selectContactAction)

    let createContactAction: UIAlertAction = UIAlertAction(title: "Create App Contact", style: .Default) { action -> Void in
        self.performSegueWithIdentifier("AddContact", sender: self)
    }
    actionSheetController.addAction(createContactAction)

    let cancelAction:UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) {
        action -> Void in
    }
    actionSheetController.addAction(cancelAction)

    self.presentViewController(actionSheetController, animated: true, completion: nil)
}


func performPickPerson(sender : AnyObject) {
    self.presentViewController(personPicker, animated: true, completion: nil)
}


func peoplePickerNavigationControllerDidCancel(peoplePicker: ABPeoplePickerNavigationController!) {
    personPicker.dismissViewControllerAnimated(true, completion: nil)
}

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

    self.contactInfoDict = ["firstName":"", "lastName":"", "company":"", "mobilePhone":"", "homePhone":"", "workPhone":"", "personalEmail":"", "workEmail":"", "street":"", "city":"", "state":"", "zipCode":"", "notes":""]

    if let firstName:String = ABRecordCopyValue(person, kABPersonFirstNameProperty)?.takeRetainedValue() as? String {
        contactInfoDict.setObject(firstName, forKey: "firstName")
        println(firstName)
    }

    if let lastName:String = ABRecordCopyValue(person, kABPersonLastNameProperty)?.takeRetainedValue() as? String {
        contactInfoDict.setObject(lastName, forKey: "lastName")
        println(lastName)
    }

    if let company:String = ABRecordCopyValue(person, kABPersonOrganizationProperty)?.takeRetainedValue() as? String {
        contactInfoDict.setObject(company, forKey: "company")
        println(company)
    }


    if let phonesRef:ABMultiValueRef = ABRecordCopyValue(person, kABPersonPhoneProperty)?.takeRetainedValue() as ABMultiValueRef? {
        for (var i = 0; i < ABMultiValueGetCount(phonesRef); i++ ) {
            var currentPhoneLabel:CFStringRef = ABMultiValueCopyLabelAtIndex(phonesRef, i).takeRetainedValue()
            var currentPhoneValue:CFStringRef = ABMultiValueCopyValueAtIndex(phonesRef, i)?.takeRetainedValue() as! CFStringRef

            if let mobileResult = CFStringCompare(currentPhoneLabel, kABPersonPhoneMobileLabel, CFStringCompareFlags.CompareCaseInsensitive) as CFComparisonResult? {
                if mobileResult == CFComparisonResult.CompareEqualTo {
                    contactInfoDict.setObject(currentPhoneValue, forKey: "mobilePhone")
                }
            }

            if let homeResult = CFStringCompare(currentPhoneLabel, kABHomeLabel, CFStringCompareFlags.CompareCaseInsensitive) as CFComparisonResult? {
                if homeResult == CFComparisonResult.CompareEqualTo {
                    contactInfoDict.setObject(currentPhoneValue, forKey: "homePhone")
                }
            }
            if let workResult = CFStringCompare(currentPhoneLabel, kABWorkLabel, CFStringCompareFlags.CompareCaseInsensitive) as CFComparisonResult? {
                if workResult == CFComparisonResult.CompareEqualTo {
                    contactInfoDict.setObject(currentPhoneValue, forKey: "workPhone")
                }
            }
        }

    }

    if let emailsRef:ABMultiValueRef = ABRecordCopyValue(person, kABPersonEmailProperty)?.takeRetainedValue() as ABMultiValueRef? {
        for (var i = 0; i < ABMultiValueGetCount(emailsRef); i++ ) {
            var currentEmailLabel:CFStringRef = ABMultiValueCopyLabelAtIndex(emailsRef, i).takeRetainedValue()
            var currentEmailValue:CFStringRef = ABMultiValueCopyValueAtIndex(emailsRef, i)?.takeRetainedValue() as! CFStringRef

            if let email = kABHomeLabel as CFStringRef? {
                if let homeResult = CFStringCompare(currentEmailLabel, kABHomeLabel, CFStringCompareFlags.CompareCaseInsensitive) as CFComparisonResult? {
                    if homeResult == CFComparisonResult.CompareEqualTo {
                        contactInfoDict.setObject(currentEmailValue as String, forKey: "personalEmail")
                    }
                }
            }
            if let workResult = CFStringCompare(currentEmailLabel, kABWorkLabel, CFStringCompareFlags.CompareCaseInsensitive) as CFComparisonResult? {
                if workResult == CFComparisonResult.CompareEqualTo {
                    contactInfoDict.setObject(currentEmailValue as String, forKey: "workEmail")
                }
            }
        }
    }

    if let addressRef:ABMultiValueRef = ABRecordCopyValue(person, kABPersonAddressProperty)?.takeRetainedValue() as ABMultiValueRef? {
        if ABMultiValueGetCount(addressRef) > 0 {
            var addressDict:NSDictionary = ABMultiValueCopyValueAtIndex(addressRef, 0)?.takeRetainedValue() as! NSDictionary

            if let street = addressDict.objectForKey(kABPersonAddressStreetKey) as? String {
                contactInfoDict.setObject(street as NSString, forKey: "street")
            }

            if let city = addressDict.objectForKey(kABPersonAddressCityKey) as? String {
                contactInfoDict.setObject(city as NSString, forKey: "city")
            }

            if let state = addressDict.objectForKey(kABPersonAddressStateKey) as? String {
                contactInfoDict.setObject(state as NSString, forKey: "state")
            }

            if let zipCode = addressDict.objectForKey(kABPersonAddressZIPKey) as? String {
                contactInfoDict.setObject(zipCode as NSString, forKey: "zipCode")
            }
        }
    }

    // Notes is not currently accessible
    if let note:String = ABRecordCopyValue(person, kABPersonNoteProperty)?.takeRetainedValue() as? String {
        println("12")
        contactInfoDict.setObject(note, forKey: "notes")
        println("Note: \(note)")
    }

    saveToContact()
}

func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, shouldContinueAfterSelectingPerson person: ABRecord!, property: ABPropertyID, identifier: ABMultiValueIdentifier) -> Bool {
    return false
}


func saveToContact(){
    self.object["username"] = PFUser.currentUser()!

    if let firstName = contactInfoDict["firstName"] as? String{
        self.object["contactFirstName"] = firstName
    }

    if let lastName = contactInfoDict["lastName"] as? String{
        self.object["contactLastName"] = lastName
    }

    if let company = contactInfoDict["company"] as? String{
        self.object["contactCompany"] = company
    }

    if let mobilePhone = contactInfoDict["mobilePhone"] as? String{
        self.object["contactMobilePhone"] = mobilePhone
    }

    if let homePhone = contactInfoDict["homePhone"] as? String{
        self.object["contactHomePhone"] = homePhone
    }

    if let workPhone = contactInfoDict["workPhone"] as? String{
        self.object["contactWorkPhone"] = workPhone
    }

    if let personalEmail = contactInfoDict["personalEmail"] as? String{
        self.object["contactPersonalEmail"] = personalEmail
    }

    if let workEmail = contactInfoDict["workEmail"] as? String{
        self.object["contactWorkEmail"] = workEmail
    }

    if let street = contactInfoDict["street"] as? String{
        self.object["contactStreet"] = street
    }

    if let city = contactInfoDict["city"] as? String{
        self.object["contactCity"] = city
    }

    if let state = contactInfoDict["state"] as? String{
        self.object["contactState"] = state
    }

    if let zipCode = contactInfoDict["zipCode"] as? String{
        self.object["contactZipCode"] = zipCode
    }

    if let notes = contactInfoDict["notes"] as? String{
        self.object["contactNotes"] = notes
    }

    self.object["contactType"] = "Lead"

    self.object["contactIsActive"] = true


    var addrObject = self.object
    self.object.pinInBackgroundWithName("Contacts")
    self.object.saveEventually{ (success, error) -> Void in
        if (error == nil) {
            println("saved in background")
        } else {
            println(error!.userInfo)
        }
    }
}


override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}

}
Community
  • 1
  • 1
blwinters
  • 1,911
  • 19
  • 40

1 Answers1

1

I just tested it and it works fine, just as you have it. This is effectively my whole code (except for getting authorization, of course):

@IBAction func doPeoplePicker (sender:AnyObject!) {
    let picker = ABPeoplePickerNavigationController()
    picker.peoplePickerDelegate = self
    self.presentViewController(picker, animated:true, completion:nil)
}

func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, 
didSelectPerson person: ABRecord!) {
    if let note = ABRecordCopyValue(person, kABPersonNoteProperty)?.takeRetainedValue() as? String {
        println(note)
    } else {
        println("no note")
    }
}

When my person has a note, I see it; when my person has no note, I see "no note".

EDIT You and I played around with this for a while, sending each other actual projects, and I observed the following difference between your implementation and mine: mine, for which fetching the note works, obtains authorization to access the address book (you'll notice that I did mention that, in the first paragraph of my original answer); yours, for which fetching the note doesn't work, doesn't obtain authorization. Hence, I suggest that this is the missing piece of the puzzle.

Here's my theory. Prior to iOS 8, you needed authorization in order to use ABPeoplePickerNavigationController. Thus, my code, which goes way back, still obtains it. The way this supposedly works in iOS 8 is that, in the absence of authorization, the people picker fetches a copy of the address book data. Well, I think that this copy is faulty (and that you should file a bug report with Apple about this). But because I have authorization, I'm accessing the actual address book data, and so my code can see the note.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • I created a test contact with a note for testing this, so I know that the Person has a note. It seems that at least one other user is having the [same issue](https://stackoverflow.com/questions/28040624/ios-swift-cant-retrive-person-note-from-addressbook), though no answer on that question yet. – blwinters May 05 '15 at 20:08
  • Maybe it makes a difference how you provide the note? I created the note in Contacts first, and then my code saw it just fine. – matt May 05 '15 at 20:16
  • Sorry, I'm not clear on your comment. I just created a standard contact on my iPhone with firstName Test, added a note, saved it, and selected it from the list of all contacts shown in my app. I've also tested it with other contacts with notes. Perhaps this is a bug with Swift. – blwinters May 05 '15 at 20:25
  • "and selected it from the list of all contacts shown in my app" Wait. Selected it how? With a people picker navigation controller? That may be the difference. Can you show more of the code you're using to configure and present and deal with the people picker navigation controller? – matt May 05 '15 at 20:34
  • I've just added the full code for the class. Thanks for taking a look. – blwinters May 05 '15 at 20:49
  • I don't see how to reproduce the difficulty. I ran my code from inside `func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecord!) {` and it printed the person's note into the console. – matt May 05 '15 at 22:26
  • I've provided complete skeleton code that works for me. Can you try the same code at your end? – matt May 05 '15 at 22:31
  • Thanks for the skeleton code, but I'm seeing "no note" for all contacts, both with and without notes. – blwinters May 06 '15 at 00:44
  • Excellent! Just what I was hoping. So now let's try to figure out why that is. What might differ between our situations? – matt May 06 '15 at 00:48
  • I've just created a completely new, single-view project and got the same result, consistently seeing "no note" on both my iPhone and the iOS Simulator. In the simulator I tested it with the John Appleseed contact, which has a note. I'm running Xcode 6.3.1 with a Deployment Target of 8.3 (if that matters). – blwinters May 06 '15 at 01:09
  • Can you post the project on github or somewhere so that I can get at it? That way we know we are running _exactly_ the same thing. – matt May 06 '15 at 01:14
  • Here is a link to the file through Dropbox: https://dl.dropboxusercontent.com/u/59235268/CopyNote.zip – blwinters May 06 '15 at 01:22
  • Okay! I'm seeing "no note". So now I have to figure out how your app differs from mine. Let me see what I can discover... – matt May 06 '15 at 01:25
  • Okay, try my modification on your machine. I think I've got it. https://www.dropbox.com/s/t39vli1n0igg684/bk2ch18p713addressBook.zip?dl=0 – matt May 06 '15 at 01:33
  • See my revised answer. – matt May 06 '15 at 01:41
  • Thank you so much. This isn't an essential part of my app, so I think I will forego the authorization request for now, but I am going to file a bug report with Apple. Diving this deep into the issue has also inspired me to buy your book. – blwinters May 06 '15 at 01:48
  • I'm glad this had a happy ending! – matt May 06 '15 at 01:49