Cocoa Touch Tutorial: Extract Address Book Address Values on iPhone OS

14 Comments

This is the first of what I hope to be several Cocoa Touch tutorials on this site.  I was doing some furious Googling last night trying to find out how to get a contact’s street address from the Address Book for an upcoming update to Take Me Home, and I realized that it’s complicated and there aren’t any good tutorials online.  So, after I figured it out, I commented it up so that hopefully, if you’re reading this, you’ll save some time that I didn’t.

Before you read this tutorial, you should go through Apple’s excellent Address Book Programming Guide for iPhone OS.  This tutorial will rely on the QuickStart application you write in the guide, so do that first.
The first thing we need to do is add an address field to the QuickStart application.  Use Interface Builder to add a new UILabel underneath the two you already have.  You may want to stretch it to fill the entire width of the screen, like so:

Add a new UILabel underneath the exisiting two.

Add a new UILabel underneath the exisiting two.

Now, add the information about this label to QuickStartViewController.h:

//
//  QuickStartViewController.h
//  QuickStart
//

#import <UIKit/UIKit.h>
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>

@interface QuickStartViewController : UIViewController <ABPeoplePickerNavigationControllerDelegate> {
    IBOutlet UILabel *firstName;
    IBOutlet UILabel *lastName;
    IBOutlet UILabel *addressLabel;
}

@property (nonatomic, retain) UILabel *firstName;
@property (nonatomic, retain) UILabel *lastName;
@property (nonatomic, retain) UILabel *addressLabel;

- (IBAction)showPicker:(id)sender;

@end

Be sure to go back into Interface Builder and connect File’s Owner in QuickStartViewController.xib to addressLabel.

Now, we have to change the method that gets called when you click on a person in the ABPeoplePicker.  As it is at the end of the QuickStart tutorial, once you select a person the picker is dismissed.  So, we do the following in QuickStartViewController.m:

- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
      shouldContinueAfterSelectingPerson:(ABRecordRef)person {
    NSString *name = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
    self.firstName.text = name;
    [name release];

    name = (NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
    self.lastName.text = name;
    [name release];

    [self dissmissModalViewControllerAnimated:YES];

    return YES;
}

Note that you have to delete the line that dismisses the modal view controller; if you don’t, the people picker is dismissed before you have a chance to get the address. When you delete it, the people picker will continue when you select a person. Next up, we have to write the method for when someone selects an address on the next screen. Here’s the method:

- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
      shouldContinueAfterSelectingPerson:(ABRecordRef)person
                                property:(ABPropertyID)property
                              identifier:(ABMultiValueIdentifier)identifier {
    // Only inspect the value if it's an address.
    if (property == kABPersonAddressProperty) {
        /*
         * Set up an ABMultiValue to hold the address values; copy from address
         * book record.
         */
        ABMultiValueRef multi = ABRecordCopyValue(person, property);

        // Set up an NSArray and copy the values in.
        NSArray *theArray = [(id)ABMultiValueCopyArrayOfAllValues(multi) autorelease];

        // Figure out which values we want and store the index.
        const NSUInteger theIndex = ABMultiValueGetIndexForIdentifier(multi, identifier);

        // Set up an NSDictionary to hold the contents of the array.
        NSDictionary *theDict = [theArray objectAtIndex:theIndex];

        // Set up NSStrings to hold keys and values.  First, how many are there?
        const NSUInteger theCount = [theDict count];
        NSString *keys[theCount];
        NSString *values[theCount];

        // Get the keys and values from the CFDictionary.  Note that because
        // we're using the "GetKeysAndValues" function, you don't need to
        // release keys or values.  It's the "Get Rule" and only applies to
        // CoreFoundation objects.
        [theDict getObjects:values andKeys:keys];

        // Set the address label's text.
        NSString *address;
        address = [NSString stringWithFormat:@"%@, %@, %@, %@ %@",
                   [theDict objectForKey:(NSString *)kABPersonAddressStreetKey],
                   [theDict objectForKey:(NSString *)kABPersonAddressCityKey],
                   [theDict objectForKey:(NSString *)kABPersonAddressStateKey],
                   [theDict objectForKey:(NSString *)kABPersonAddressZIPKey],
                   [theDict objectForKey:(NSString *)kABPersonAddressCountryKey]];

        self.addressLabel.text = address;

        // Memory management.
        [theDict release];

        // Return to the main view controller.
        [ self dismissModalViewControllerAnimated:YES ];
        return NO;
    }

    // If they didn't pick an address, return YES here to keep going.
    return YES;
}

Let’s go through that in more detail.  The method gives us the following information: an ABRecordRef of the person we’ve selected, an ABPropertyID of the property slected (in this case, we ensure that it’s the address) and an ABMultiValueIdentifier of which address we’ve selected.  It is important to note that the ABPropertyID is equal to kABPersonAddressProperty when you select any address; that is, there is only one address property. This one address property holds the values in an ABMultiValue, each at a specific index.  Here are the steps we take in the code:

  1. The first thing we do is define our ABMultiValue, multi, and copy the contents of the selected value into it.
  2. Then we define an NSArray, theArray, into which to copy the multiple values.  But which one do we want?
  3. Each address has an identifier, which the method gives to us as identifier, but we reference them by index when getting them out of the array.  So, we need to create an index (which we’ll store as an unsigned integer), theIndex, and set it to the return value of the ABMultiValueGetIndexForIdentifier function.  Now that we have the index, we know which value of the array to store .  They’re stored as type CFDictionary, which have key-value pairs for us to use, so we define an NSDictionary, theDict to put them into.
  4. First, we need to know how many key-value pairs there are, so we use the count method and store the return value in an unsigned integer, theCount.  Be sure that this variable doesn&rquo;t change—you don’t want to assume that there are more members in the array than there actually are, as that can lead to nasty memory problems. For that reason I’ve defined it as a constant.
  5. Now, we define two NSString arrays, keys[theCount] and values[theCount], and then we’re ready for action.
  6. Next we use the NSDictionary getObjects: andKeys: function to copy the keys and values. The function copies the data, and we can construct our street address.  For the purpose of this example, I’m going to make the address a single line, but you do with it what you want.
  7. Finally, we create a final NSString to put the formatted address into, pull the values out of the dictionary into the appropriate place, and we’re all done!

14 Comments (+add yours?)

  1. AGUSTIN
    Feb 09, 2009 @ 19:51:26

    Jeff,
    This is Agustin from Mexico, City.

    I found your blog, and the code is excellent. Works great.

    I am sure you can help me to obtain the value from the email address selected.

    I believe it most be using ABMultiValueGetFirstIndexOfValue. But how do we use it?

    Thanks

    Reply

  2. Jeff Kelley
    Feb 09, 2009 @ 20:52:39

    Agustin,

    Are you trying to get a person’s e-mail address from a PeoplePicker? If so, you can replace kABPPersonAddressProperty with kABPersonEmailProperty. You’ll have to make some minor changes to the code below; the address property is a MultiValue property that contains arrays, while the e-mail address property is a MultiString property that just returns multiple strings. See here for more information:

    http://developer.apple.com/iphone/library/documentation/AddressBook/Reference/ABRecordRef_iPhoneOS/Reference/reference.html#//apple_ref/c/macro/kABMultiStringPropertyType

    Reply

  3. AGUSTIN
    Feb 10, 2009 @ 15:56:17

    Jeff,

    Finally I am able to get a person’s email-address from a PeoplePicker. I use some of your code with minor changes extracting the email value and the email label localized.
    Here is the code if you like to publish it.
    Thanks for your help.

    // Only inspect the value if it’s an email
    // Obtains the email addres ana localized label from a PeoplePicker

    if (property == kABPersonEmailProperty) {

    ABMultiValueRef emails = ABRecordCopyValue(person, property);
    CFStringRef emailValueSelected = ABMultiValueCopyValueAtIndex(emails, identifier);
    CFStringRef emailLabelSelected = ABMultiValueCopyLabelAtIndex(emails, identifier);

    CFStringRef emailLabelSelectedLocalized = ABAddressBookCopyLocalizedLabel(ABMultiValueCopyLabelAtIndex(emails, identifier));

    NSLog(@”\n EmailValueSelected = %@ \n EmailLabelSelected = %@ \n \EmailLabeSelectedlLocalized = %@”, emailValueSelected, emailLabelSelected, emailLabelSelectedLocalized);

    self.emailLabel.text = (NSString *) emailLabelSelectedLocalized;
    self.emailValue.text = (NSString *) emailValueSelected;
    // Return to the main view controller.
    [ self dismissModalViewControllerAnimated:YES ];

    return NO;
    }

    Reply

  4. Samuel L.
    Apr 24, 2009 @ 06:09:36

    After reading the article, I just feel that I need more info. Could you share some more resources please?

    Reply

  5. Pankaj
    Jun 01, 2009 @ 05:20:30

    Hello All,

    I am able to read the address book contact list and its data successfully in my application, when we i run the address book code several times then at some point in time the application scratched in the simulator?

    Do any body know, where is the issue?

    Regards,
    Pankaj

    Reply

  6. Robert Conn
    Sep 23, 2009 @ 16:18:39

    Thanks for this – really helpful.

    Was just wondering why you needed to create an array of keys and values and populate those from the dictionary? They don’t seem to be used anywhere else and you continue to use the dictionary directly to generate the address for display in the label.

    Reply

  7. Jeff Kelley
    Sep 23, 2009 @ 21:10:07

    Robert, it’s been a while since I wrote this, but I think you’re talking about this line:

    NSArray *theArray = [(id)ABMultiValueCopyArrayOfAllValues(multi) autorelease];

    This is for situations where the person has multiple addresses. Each item in the array is a dictionary.

    Reply

  8. Michael
    Dec 29, 2009 @ 15:48:52

    This has been great however, when I load it onto the phone, when I select an address…it launches google maps. obviously I’m missing something. Ideas?

    Reply

  9. Jeff Kelley
    Dec 29, 2009 @ 15:53:25

    Michael, I haven’t experienced that before. I don’t know what you’re doing that would make that happen. I’d recommend posting some code on stackoverflow.com with the iPhone tag; that’s a little more visible than my blog and more likely to get answered well. Good luck!

    Reply

  10. Michael
    Jan 03, 2010 @ 07:52:10

    Thanks Jeff, I just posted the code on stackoverflow.

    Reply

  11. Curtis
    Feb 01, 2010 @ 13:28:18

    Michael,

    Change the boolean to false on shouldContinueAfterSelectingPerson. Doc reference attached below. I’ve been learning and coding-by-google-search for several days now, so I’m very happy to finally answer a question for someone else. I hope this helps you.

    If the user selects a property, the people picker calls the method peoplePickerNavigationController:shouldContinueAfterSelectingPerson:property:identifier: of the delegate to determine if it should continue. To perform the default action (dialing a phone number, starting a new email, etc.) for the selected property, return YES. Otherwise return NO and dismiss the picker.

    http://developer.apple.com/iphone/library/documentation/ContactData/Conceptual/AddressBookProgrammingGuideforiPhone/400-UI_Controllers/UI_Controllers.html#//apple_ref/doc/uid/TP40007744-CH5-SW1

    Reply

Leave a Reply