Accessing the Address Book
There are two parts to
accessing the address book: displaying a view that allows the user to
choose a contact (an instance of the class ABPeoplePickerNavigationController) and reading the data that corresponds to that contact. Two steps...two frameworks that we need to add. Let’s do that now.
Adding the Address Book Frameworks and Delegate
Right-click the
Frameworks group in your BestFriend project and choose Add, Existing
Frameworks from the contextual menu. When prompted, find and add the
AddressBook.framework and AddressBookUI.framework (as shown in Figure 2).
If the frameworks don’t show up in the Frameworks group, drag them there to keep things tidy.
We also need to import the headers for the Address Book and Address Book UI frameworks and indicate that we implement the ABPeoplePickerNavigationControllerDelegate protocol because our BestFriendViewController will be the delegate for our Address Book People Picker, and this protocol is required of its delegate.
Modify the BestFriendViewController.h file, adding these lines to after the existing #import line:
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>
Next, update the @interface line, adding <ABPeoplePickerNavigationControllerDelegate> to show that we are conforming to the ABPeoplePickerNavigationControllerDelegate protocol:
@interface BestFriendViewController : UIViewController
<ABPeoplePickerNavigationControllerDelegate> {
Displaying the Address Book
When the user presses the
button to choose a buddy, we want to show the Address Book Person Picker
modal view controller, which will provide the user with the familiar
interface from the Contacts application.
Add the IBAction method shown in Listing 2 to the Best_FriendViewController.m file.
Listing 2.
1: - (IBAction)newBFF:(id)sender {
2: ABPeoplePickerNavigationController *picker;
3:
4: picker=[[ABPeoplePickerNavigationController alloc] init];
5: picker.peoplePickerDelegate = self;
6:
7: [self presentModalViewController:picker animated:YES];
8:
9: [picker release];
10: }
|
In line 2, we declare picker as an instance of ABPeoplePickerNavigationController—a GUI object that displays the system’s address book. Lines 4 and 5 allocate the object and set its delegate to our BestFriendViewController (self).
Line 7 displays the people picker as a modal view over top of our existing user interface.
After the view is displayed, the picker object is released in line 9.
Handling Other Address Book Interactions
For the BestFriend
application, we need to know only the friend the user has selected; we
don’t want the user to go on and select or edit the contact’s
properties. So we will need to implement the delegate method peoplePickerNavigationContoller:peoplePicker:shouldContinueAfterSelectingPerson to return NO
when it is called—this will be our “workhorse” method. We also need our
delegate methods to dismiss the person picker modal view and return
control of the UI back to our BestFriendViewController.
Add the two ABPersonPickerViewControllerDelegate protocol methods in Listing 3
to the Best_FriendViewController.m file. We need to include these to
handle the conditions of the user cancelling without picking someone (peoplePickerNavigationControllerDidCancel) and the user drilling down further than a “person” to a specific attribute (peoplePickerNavigationController:shouldContinueAfterSelectingPerson:property:identifier). Because we’re going to capture the user’s selection before he or she even can drill down, the second method can just return NO—it’s never going to get called anyway.
Listing 3.
// Called after the user has pressed cancel
// The delegate is responsible for dismissing the peoplePicker
- (void)peoplePickerNavigationControllerDidCancel:
(ABPeoplePickerNavigationController *)peoplePicker {
[self dismissModalViewControllerAnimated:YES];
}
// Called after a value has been selected by the user.
// Return YES if you want default action to be performed.
// Return NO to do nothing (the delegate is responsible for dismissing the
peoplePicker).
- (BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier {
//We won't get to this delegate method
return NO;
}
|
Choosing, Accessing, and Displaying Contact Information
If the user doesn’t cancel the selection, the peoplePickerNavigationContoller:shouldContinueAfterSelectingPerson: delegate method will be called, and with it we are passed the selected person as an ABRecordRef. An ABRecordRef is part of the Address Book framework that we imported earlier.
We can use the C functions
of the Address Book framework to read the data about this person from
the address book. For this example, we read four things: the person’s
first name, picture, email address, and ZIP code. We will check whether
the person record has a picture before attempting to read it.
Notice that we don’t access the person’s attributes as the native Cocoa objects you might expect (namely, NSString and UIImage,
respectively). Instead, the name string and the photo are returned as
Core Foundation C data, and we convert it using the handy ABRecordCopyValue function from the Address Book framework and the imageWithData method of UIImage.
For the email address and ZIP
code, we must deal with the possibility of multiple values being
returned. For these pieces of data, we’ll again use ABRecordCopyValue to grab a reference to the set of data, and the functions ABMultiValueGetCount to make sure that we actually have an email address or ZIP code stored with the contact, and ABMultiValueCopyValueAtIndex to copy the first value that we find.
Sounds complicated? It’s not the prettiest code, but it’s not difficult to understand.
Add the final delegate method peoplePickerNavigationController:shouldContinueAfterSelectingPerson to the BestFriendViewController.m file, as shown in Listing 4.
Listing 4.
1: - (BOOL)peoplePickerNavigationController:
2: (ABPeoplePickerNavigationController *)peoplePicker
3: shouldContinueAfterSelectingPerson:(ABRecordRef)person {
4:
5: // Declare variables for temporarily handling the string data
6: NSString *friendName;
7: NSString *friendEmail;
8: NSString *friendZip;
9:
10: friendName=(NSString *)
11: ABRecordCopyValue(person, kABPersonFirstNameProperty);
12: name.text = friendName;
13: [friendName release];
14:
15:
16: ABMultiValueRef friendAddressSet;
17: NSDictionary *friendFirstAddress;
18: friendAddressSet = ABRecordCopyValue(person, kABPersonAddressProperty);
19:
20: if (ABMultiValueGetCount(friendAddressSet)>0) {
21: friendFirstAddress = (NSDictionary *)
22: ABMultiValueCopyValueAtIndex(friendAddressSet,0);
23: friendZip = [friendFirstAddress objectForKey:@"ZIP"];
24: [friendFirstAddress release];
25: }
26:
27: ABMultiValueRef friendEmailAddresses;
28: friendEmailAddresses = ABRecordCopyValue(person, kABPersonEmailProperty);
29:
30: if (ABMultiValueGetCount(friendEmailAddresses)>0) {
31: friendEmail=(NSString *)
32: ABMultiValueCopyValueAtIndex(friendEmailAddresses, 0);
33: email.text = friendEmail;
34: [friendEmail release];
35: }
36:
37: if (ABPersonHasImageData(person)) {
38: photo.image = [UIImage imageWithData:
39: (NSData *)ABPersonCopyImageData(person)];
40: }
41:
42: [self presentModalViewController:picker animated:YES];
43: return NO;
44: }
|
Let’s walk through the logic we’ve implemented here. First, note that when the method is called, it is passed a person variable of the type ABRecordRef—this is a reference to the person who was chosen and will be used throughout the method.
Lines 6–8 declare variables
that we’ll be using to temporarily store the name, email, and ZIP code
strings that we retrieve from the address book.
Lines 10–11 use the ABRecordCopyVal method to copy the kABPersonFirstNameProperty property, as a string, to the friendName variable. Lines 12–13 set the name UILabel to this string, and then friendName is released.
Accessing an address is a bit
more complicated. We must first get the set of addresses (each a
dictionary) stored for the person, access the first address, then access
a specific field within that set. Within address book, anything with
multiple values is represented by a variable of type ABMultiValueRef. We declare a variable, friendAddressSet, of this type in line 16. This will reference all addresses of the person. Next, in line 17, we declare an NSDictionary called friendFirstAddress. We will store the first address from the friendAddressSet
in this dictionary, where we can easily access its different fields
(such as city, state, ZIP, and so on). In Line 18, we populate friendAddressSet by again using the ABRecordCopyVal function on the kABPersonAddressProperty of person.
Lines 20–25 execute only if ABMultiValueGetCount returns a copy of greater than zero on the friendAddressSet. If it is zero, there are no addresses associated with the person, and we should move on. If there are addresses, we store the first address in friendFirstAddress by copying it from the friendAddressSet using the ABMultiValueCopyValueAtIndex
method in lines 21–22. The index we use with this function is 0—which
is the first address in the set. The second address would be 1, third 2,
and so on.
Line 23 uses the NSDictionary method objectForKey to grab the ZIP code string. The key for the ZIP code is simply the string "ZIP". Review the address book documentation to find all the possible keys you may want to access. Finally, Line 24 releases the friendFirstAddress dictionary.
Watch Out!
In case you’re wondering, the code here is not yet complete! We don’t actually do
anything with the ZIP code just yet. This ties into the map function we
use later, so, for now, we just get the value and ignore it.
This entire process is implemented again in lines 27–35 to grab the person’s
first email address. The only difference is that rather than email
addresses being a set of dictionaries, they’re simple a set of strings.
This means that once we verify that there are email addresses stored for
the contact (line 30), we can just copy the first one in the set and
use it immediately as a string (lines 31 and 32). Line 33 sets the email UILabel to the user’s email address.
After all of that, you must be
thinking to yourself, “Ugh, it’s got to be a pain to deal with a
person’s photo.” Wrong! That’s actually the easy part! Using the ABPersonHasImageData function in line 37, we verify that person has an image stored. If he or she does, we copy it out of the address book using ABPersonCopyImageData and use that data along with the UIImage method imageWithData to return an image object and set the photo image within the interface. All of this occurs in lines 38–39.
As a final step, the modal view is dismissed in line 42.
Whew! A few new
functions were introduced here, but once you get the pattern down,
moving data out of address book becomes almost simple.
So, what about that ZIP code? What are we going to do with it? Let’s find out now by implementing our interactive map!