Accessing Address Book Records

After you have a record, you can retrieve the data within it. This chapter shows you how the data is organized and how to access it. It shows how to access properties from a records property list, how to handle properties that can have more than one value (such as addresses and phone numbers), how to get localized names for properties and labels, and how to associate a picture with a person.

Using Property Lists

Both groups and people store their data in property lists. This lets your application add properties to the Address Book records that other applications will ignore. See “Adding Properties to Address Book Records.”

Table 1  The documentation for property-list constants

Documentation

Class

Language

“Default Record Properties” in Address Book Objective-C Constants Reference

ABRecord

Objective-C

“Default Person Properties” in Address Book Objective-C Constants Reference

ABPerson

Objective-C

“Default Group Properties” in Address Book Objective-C Constants Reference

ABGroup

Objective-C

“Constants” in ABPerson C Reference

ABPersonRef

Procedural C

“Constants” in ABGroup C Reference

ABGroupRef

Procedural C

Using Multivalue Lists

Many properties can have multiple values. For example, a person can have several addresses, including work, home, summer home, and mailing addresses. These properties are stored as multivalue lists, of type ABMultiValue. Each item in a multivalue list has a numeric index, a unique identifier, a string label (such as Home or Work), and a value. Every value in the multivalue list must be of the same type. The label does not need to be unique; after all, someone could have more than one home or work address.

You use the numeric index to access items in a multivalue list, but these indices may change as the user adds and removes values. If you want to save a reference to a specific value, use the unique identifier, which is guaranteed not to change. You can convert back and forth between indices and unique identifiers:

Each multivalue list also has a primary value, which is the item the user most strongly associates with that person. For example, friends may have both home and work addresses, but the home address is their primary address. And coworkers may have both home and work phone numbers, but the work number is their primary number.

The Picture Associated with a Person

A person may also have an associated picture or image. The image is not actually stored in the Address Book database (a property list)—it’s stored in a separate image file. This means you need to use different methods to access the image data. You can set a person’s image using the setImageData: method, or get an image using the imageData method. Use the NSImage initWithData: method to convert the NSData object returned by the imageData method to an NSImage object.

The Address Book framework locates images through a specific search hierarchy, in this order:

  1. Check for an image set specifically by the user.

  2. Check Directory Services for the local user’s login picture.

  3. Check for an image in /Network/Library/Images/People/email, where email is the user’s primary email address.

  4. Check for an image from the user’s MobileMe account, first against the cache at ~/Library/Caches/com.apple.AddressBook/email and then against the MobileMe servers.

Image files may be local or remote. Local images are any images in .../Library/Images/People and any images the user has set using the Address Book application. Remote images are images stored on the network. Remote images take time to download, so an asynchronous API for fetching remote images is provided.

Use the beginLoadingImageDataForClient: method if an image file is not local and you want to perform an asynchronous fetch. You pass a client object that implements the ABImageClient protocol as an argument to this method. The beginLoadingImageDataForClient: method returns an image tracking number. A consumeImageData:forTag: message is sent to your client object when the fetch is done. Implement this method to handle the new fetched image. Use the cancelLoadingImageDataForTag: class method if you want to cancel an asynchronous fetch.

Use the beginLoadingImageDataForClient: method if an image file is not local and you want to perform an asynchronous fetch:

  1. Pass a client object that implements the ABImageClient protocol as an argument to this method.

  2. The beginLoadingImageDataForClient: method returns an image tracking number.

  3. A consumeImageData:forTag: message is sent to your client object when the fetch is done. Implement this method to handle the new fetched image.

  4. Use the cancelLoadingImageDataForTag: class method if you want to cancel an asynchronous fetch.

Getting Localized Names for Properties and Labels

You can find the localized name for any of the default property names and labels that are listed in Address Book Objective-C Constants Reference. The functions ABLocalizedPropertyOrLabel and ABCopyLocalizedPropertyOrLabel returned a name that is localized for the user’s selected language.

You must handle the localization of the names for the properties and labels you create; the Address Book framework does not provide any specific support for this.

An Example

Listing 1 is an Objective-C code sample that retrieves the country for the primary address of the logged-in user. If the country is a null string, it sets the country to USA.

Listing 1  Changing a person’s address

ABPerson *aPerson = [[ABAddressBook sharedAddressBook] me];
ABMutableMultiValue *anAddressList =
    [[aPerson valueForProperty:kABAddressProperty] mutableCopy];
NSUInteger primaryIndex =
    [anAddressList indexForIdentifier:[anAddressList primaryIdentifier]];
NSMutableDictionary *anAddress =
    [[anAddressList valueAtIndex:primaryIndex] mutableCopy];
NSString *country =
    (NSString*) [anAddress objectForKey:kABAddressCountryKey];
if ([country isEqualToString:@""]) {
    [anAddress setObject:@"USA" forKey:kABAddressCountryKey];
    [anAddressList replaceValueAtIndex:primaryIndex withValue:anAddress];
    [aPerson setValue:anAddressList forProperty:kABAddressProperty];
    [[ABAddressBook sharedAddressBook] save];
}

Using the People Picker

The people picker is a view that provides access to the contents of a user’s address book from any application. It offers a searchable, selectable list of people and groups that can be customized for your application.

To use a people picker in a Cocoa application, drag it from the library palette into your window. If you need to, you can create an instance of ABPeoplePickerView programmatically instead. There are two ways to set up the behavior of the people picker view—from Interface Builder and programmatically.

To set up the behavior from Interface Builder, open the Get Info panel. The attributes you can set include which columns are displayed by the view, whether or not multiple selections are allowed, whether or not group selections are allowed, and the autosave name for the view. These changes are reflected immediately within Interface Builder. Using the Test Interface feature of Interface Builder, a people picker view will display the address book of the logged-in user.

To set up the behavior programmatically, create an outlet of the type ABPeoplePickerView from your controller and connect it to the people picker view. Then use the appropriate instance methods to change attributes of the picker.

For Cocoa applications, the people picker also provides methods for using autosave data, so that it can retain the filter selections and column positions. Use the ABPeoplePickerView methods autosaveName and autosaveName.

Using the C API, the people picker is available only in a window form and cannot be used as a custom view. It must be created with ABPickerCreate and made visible with ABPickerSetVisibility, as follows:

ABPickerRef peoplePicker = ABPickerCreate();
ABPickerSetVisibility(peoplePicker, true);

People Picker Example

The code from this example should be placed in the window controller for the people picker. Developers using the C API should refer to the ABPicker C Reference to construct the analogue for their applications; most of the functions are named similarly. Instead of using notifications, you will need to register event handlers to handle changes to the window.

The following listing shows how you can set the attributes of a people picker programmatically. This code is typically part of the awakeFromNib method. All of these settings are also available in the attributes inspector, in Interface Builder.

    // Disallow multiple selections in the name list.
    [peoplePicker setAllowsMultipleSelection:NO];
 
    // Add the e-mail and telephone properties to the view.
    // By default, the people picker displays only the
    // Name column.
    [peoplePicker addProperty:kABEmailProperty];
    [peoplePicker addProperty:kABPhoneProperty];

The following listing shows how to register for a notification when the user selects a person record in the people picker. This code is typically part of the awakeFromNib method.

    NSNotificationCenter* center;
    center = [NSNotificationCenter defaultCenter];
 
    // Set up a responder for one of the four available notifications,
    // in this case to tell us when the selection in the people picker
    // has changed.
    [center addObserver:self
            selector:@selector(recordChanged:)
            name:ABPeoplePickerNameSelectionDidChangeNotification
            object:peoplePicker];

The following listing shows an example of how to respond to the user selecting a person record in the people picker:

- (void)recordChanged:(NSNotification*)notification {
    NSArray *array;
    NSImage *personImage;
    NSString *personFirstName;
    NSString *personLastName;
 
    array = [peoplePicker selectedRecords];
    NSAssert([array count] == 1,
             @"Picker returned multiple selected records");
    ABPerson *person = [array objectAtIndex:0];
 
    personImage = [[NSImage alloc] initWithData:[person imageData]];
    personFirstName = [person valueForProperty:kABFirstNameProperty],
    personLastName = [person valueForProperty:kABLastNameProperty];