In addition to standard user interface controls and media components that you’d see on any computer, the iPhone SDK provides a number of tightly focused developer solutions specific to iPhone and iPod touch delivery. The most useful of these include Address Book access, allowing you to programmatically access and manage the contacts database. This chapter introduces the Address Book and demonstrates how to use its frameworks in your applications. You read about accessing information on a contact-by-contact basis, how to modify and update contact information, and how to use predicates to find just the contact you’re interested in. This chapter also covers the GUI classes that provide interactive solutions for picking, viewing, and modifying contacts. By the time you’ve read through this chapter, you’ll have discovered the address book from the bottom up.
The iPhone SDK provides not one but two address book frameworks. These are AddressBook.framework and AddressBookUI.framework. As their names suggest, they occupy distinct niches in the iPhone SDK. AddressBook provides low-level C-based structures and routines for accessing contact information from the iPhone’s onboard SQLite databases. AddressBookUI offers high-level Objective-C based UIViewController
browser objects to present to users. Both frameworks are small. They provide just a few classes and data types.
On the iPhone, contact data resides in the home Library folder. On the Macintosh-based iPhone simulator, you can freely access these files in ~/Library/Application Support/iPhone Simulator/User/Library/AddressBook. The two files, AddressBook.sqlitedb and AddressBookImages.sqlitedb use standard SQLite to store contact information and, in the latter file, optional contact images. On the iPhone, the same files live in /var/mobile/Library/AddressBook, that is, out of the application sandbox. You must use the two Address Book frameworks to query or modify the user’s contact information rather than accessing these files directly.
The AddressBookUI framework provides several precooked view controllers that interact with the onboard contacts database. These interfaces include a general people picker, a contact viewer, and a contact editor. You set a delegate and then push these controllers onto your navigation stack or display them modally, as shown in the recipes in this chapter.
Like the image picker and video camera controllers you saw in Chapter 7, “Working with Images,” and Chapter 15, “Audio, Video, and MediaKit,” the AddressBookUI controllers are not very flexible. Apple intends you to use them as provided, with little or no customization from the developer. What’s more, they require a certain degree of low-level programming prowess. As you see in this chapter, these classes interact with the underlying Address Book in circuitous ways.
In the C-based AddressBook framework, the ABRecordRef
type provides a core contact structure. This record stores all information for each contact, including name, e-mail, phone numbers, and so forth. Every record corresponds to a complete address book contact. Query the address book for the number of objects currently stored in its database. Despite the name, the ABAdressbookCreate()
function does not create a new address book; it creates a reference to the system address book.
Recover individual records by calling the ABAddressBookCopyArrayOfAllPeople()
function. The following method retrieves those records as an array and then adds each record into an ABContact
object. ABContact
is a custom Objective-C wrapper developed for this book. Objective-C wrappers provide easy integration between the C-based address book calls and normal Cocoa Touch development and memory management. The full source for this, and a couple of other wrapper classes, can be found in the sample code for this chapter.
The ABContact
class hides an internal ABRecordRef
, the CF type that corresponds to each contact record. The remaining portion of the wrapper involves nothing more than generating properties and methods that allow you to reach into the ABRecordRef
to set and access its subrecords.
Nearly all ABRecordRef
functions use the ABPerson
prefix. This prefix corresponds to the ABPerson
class that is available on the Macintosh but not on the iPhone. So while the function calls are ABPerson
-centric, all the data affected by these calls are actually ABRecordRef
instances. The reason for this becomes clearer when you notice that the same ABRecordRef
structure is used in the AddressBook framework to represent both people (individual contacts, whether people or businesses) and groups (collections of contacts, such as work colleagues and personal friends). The SDK provides ABGroup
functions as well as ABPerson
ones. You read about groups later in this section.
Each ABRecord
stores a number of simple string values that represent, along with other items, a person’s name, title, job, and organization. Retrieve these items by copying field values from the record. The following method uses a property constant (ABPropertyID
) that identifies the requested field in the record. The method copies this value, casts it to a string, and returns that content.
The 13 string-based fields you can recover in this fashion are as follows. These identifiers are defined as constant integers in the ABPerson.h header file. They identify fields in an ABRecordRef
that store a single string for each property.
• kABPersonFirstNameProperty
• kABPersonLastNameProperty
• kABPersonPrefixProperty
• kABPersonSuffixProperty
• kABPersonNicknameProperty
• kABPersonFirstNamePhoneticProperty
• kABPersonLastNamePhoneticProperty
• kABPersonMiddleNamePhoneticProperty
• kABPersonOrganizationProperty
• kABPersonJobTitleProperty
• kABPersonDepartmentProperty
• kABPersonNoteProperty
Setting string-based properties proves to be just as simple as retrieving them. Cast the string you want to set to a CFStringRef
. Use ABRecordSetValue()
to store the data back into the record. Take note that these calls do not update the address book. They only change the data within the record. If you want to store a user’s contact information, you have to write that information back to the address book. A solution for doing so follows later in this section.
In addition to the string properties you just saw, the address book stores three key dates: an optional birthday, the date the record was created, and the date the record was last modified. These items use the following property constants.
• kABPersonCreationDateProperty
• kABPersonModificationDateProperty
Access these items exactly as you would with strings but cast to and from NSDate
instances instead of NSString
instances. Although you can, theoretically, modify the latter two properties, you’re best allowing the address book to handle them.
Each person may have multiple e-mail addresses, phone numbers, and important dates (beyond the birthday singleton) associated with his or her contact. ABPerson
uses a multivalue structure to store lists of these items. Each multivalue item is basically an array. You can recover each array from the record via its property identifier. Instead of returning a string, the record returns a CFArrayRef
.
The multivalue property identifiers you may work with are as follows:
• kABPersonEmailProperty
• kABPersonPhoneProperty
• kABPersonURLProperty
• kABPersonAddressProperty
• kABPersonInstantMessageProperty
The first three of these items (e-mail, phone, and URL) store multistrings—that is, arrays of strings. Their associated type is the kABMultiStringPropertyType
. Each multivalue type plays an important role in storing data back to the record. The type is used to allocate memory and determine the size for each field within the record.
The next item, the date property, stores an array of dates using kABMultiDateTimePropertyType
. Both the address and instant message properties consist of arrays of dictionaries and use kABMultiDictionaryPropertyType
.
“Related names” represents another multistring property but one that does not actually get used on the iPhone Contacts application at this time. It uses the kABPersonRelatedNamesProperty
constant and stores names and their relationships, for example, Mary Ball Washington might be stored in George Washington’s contact using the kABPersonMotherLabel
. See ABPerson.h for a full list of relation constants.
It’s straightforward to retrieve an array of values for any of these properties. Just copy the property out of the record (using ABRecordCopyValue()
) and then break it down into its component array. The address book provides a function that copies the array from the property into a standard CFArrayRef
.
Although you might think you’ve retrieved all the information with those two calls, you have not. Value retrieval alone is not sufficient for working with multivalued items. Each element stored in a multivalue array uses a label as well as a value. Figure 18-1 shows part of an address book contact page. Grouped items use labels to differentiate the role for each e-mail, phone number, and so forth. This contact has three phone numbers and three e-mail address, each of which displays a label indicating the value’s role.
You must copy the labels from the property as well as the values to retrieve all the information stored for each multivalue property. The following method copies each label by its index and adds it to a labels array. Together, the labels and the values comprise a complete multivalue collection.
Saving into multivalue objects works the same way but in reverse. To store multivalued items into a record, you must transform your Cocoa Touch objects into a form the record can work with. The following method expects an array of dictionaries. Each dictionary must contain two keys: value and label. The objects for these keys correspond to the value and label retrieved from the original multivalued property. This code iterates through that array of dictionaries and adds each value and label to the mutable multivalue object.
Notice how this method creates the multivalue object using an ABPropertyType
supplied as the method parameter. This is where the various kinds of multistring types come into play.
For example, you can populate an e-mail property with strings and labels using kABMultiStringPropertyType
. This method calls the one that creates a multivalue object, providing both value-label dictionaries and the multiproperty type to use. Once that multivalue item is created, it is passed to another method to be set.
Assigning a multivalue object to a record is simple. Use the standard ABRecordSetValue()
call. The following method performs the assignment of a multivalued object to a property within a record. There is essentially no difference between this call and the calls that set a single date or string property. All the work is done in creating the multivalue item in the first place.
Both address and instant message (SMS) properties use dictionaries rather than strings or dates. This adds an extra step to the creation of a multivalue array. You must populate a set of dictionaries and then add them to an array along with their labels. Figure 18-2 illustrates this additional layer. As this figure shows, e-mail multivalue items consist of an array of label-value pairs, where each value is a single string. In contrast, addresses use a separate dictionary, which is bundled into the value item.
Here’s an example that demonstrates the steps involved in creating a two-address multivalue item. This code builds the dictionaries and then adds them, along with their labels, to a base array. The array created by this code corresponds to the multivalue address object shown on the right side of Figure 18-2.
This code relies on convenience methods to create both the address dictionaries and the value/label dictionaries used for the multivalue array. The following methods produce the label/value dictionaries, and the address and SMS dictionaries. Notice how the keys for the address and SMS dictionaries are predefined, using address book key constants.
Each record in the address book may be associated with an optional image. You can copy image data to and from each record. The ABPersonHasImageData()
function indicates whether data is available for a given record. Use this to test whether you can retrieve image data.
Image data is stored as CFData
, which is toll-free bridged with NSData
. As the UIImage
class fully supports converting images into data and creating images from data, you just need to cast that data as needed. Use the UIImagePNGRepresentation()
function to transform a UIImage
instance into an NSData
representation. Use imageWithData:
to create a new image from NSData
.
The ABPersonCreate()
function returns a new ABRecordRef
instance. This record exists outside the address book and represents a freestanding data structure. To date, all the methods you’ve seen in this chapter have modified individual records, but none so far has actually saved a record to the address book. Keep that in mind as you look at this convenience method that returns a newly initialized contact.
To write new information to the address book takes two steps. You must add the record and then save the address book. New iPhone developers often forget the second step, leading to an address book that appears to resist changes. This method adds a new contact to the address book, first by adding the record and then by saving the changes.
You cannot overwrite new contact information to a contact that already exists in the address book. If you create a new “George Washington” record and attempt to save it to an address book that already has a “George Washington” record, you’ll fail. That’s because the new record does not have the same record identifier as the original. The mismatch between the two records causes the error. Here is how you query a record for its unique identifier.
- (ABRecordID) recordID {return ABRecordGetRecordID(record);}
You can update contact information only by reading out the existing record, modifying it, and saving it (this approach is used in Recipe 18-7), or by removing the record and then adding back a new version.
Removing a record from the address book requires a save step, just like adding a record. Once removed, the record still exists as an object, but it no longer is stored in the address book database. Here’s how you can remove a record from the address book.
The default address book framework allows you to perform a prefix search across records. This function returns an array of records whose composite names (typically first name appended by last name, but localizable to countries where that pattern is reversed) match the supplied string.
NSArray *array = (NSArray *)ABAddressBookCopyPeopleWithName(
addressBook, CFSTR("Eri"));
Address Book routines are written using C-based Core Foundation libraries. Many classes live in both the Cocoa Touch Foundation and Core Foundation worlds. For example, an NSArray*
pointer corresponds to Core Foundation CFArrayRef
. These classes are “toll free bridged.” They provide identical structure and functionality, and you can cast one to the other without penalty. The snippet shown above casts the array reference returned by the Core Foundation ABAddressBookCopyPeopleWithName()
into an NSArray
pointer for easier Objective-C wrapping.
It’s far easier to combine a set of properties, like those provided in the custom ABContact
class, with NSPredicate
instances. The following code matches a string against a contact’s first name, middle name, last name, and nickname. The predicate uses property names to define how it matches or rejects contacts. It does this using a case and diacritical insensitive match ([cd]
) that compares against all points within each string (contains
), not just the start (begins with
).
Apple’s Predicate Programming Guide offers a comprehensive introduction to predicate basics. It demonstrates how to create predicates and use them in your application.
Groups allow you to collect contacts into related sets such as work, home, and other natural groupings. Each group is nothing more than another ABRecord
but with a few special properties. Groups don’t store names, addresses, and phone numbers. Instead, they store a reference to other contact records.
Are you unfamiliar with groups on the iPhone? That’s because Apple’s iPhone Contact application doesn’t provide a way to create them. The only way to add groups of contacts to your iPhone is via the SDK or by synchronizing an address book from your Macintosh.
You can count the number of groups in the current address book by retrieving the group records, as shown in this method. There’s no direct way to query the number of groups as there is with the number of person contacts. The following methods are part of an ABGroup
wrapper class that provides an Objective-C wrapper for address book groups like ABContact
wraps address book contacts.
Create groups using the ABGroupCreate()
function. This function returns an ABRecordRef
in the same way that ABPersonCreate()
does. The difference lies in the record type. For groups, this property is set to kABGroupType
instead of kABPersonType
.
Add and remove members of a group by calling ABGroupAddMember()
and ABGroupRemoveMember()
. These calls affect records only and are not stored until you save the address book.
Each member of a group is a person, or using this chapter’s ABContact
terminology, a contact. This method scans through a group’s members and returns an array of ABContact
instances, each initialized with the ABRecordRef
for a group member.
Every group has a name. It is the primary group property that you can set and retrieve. It uses the kABGroupNamePropert
identifier, and it otherwise works just like the contacts properties.
The sample code that accompanies this section includes source for three wrapper classes. The snippets shown throughout this discussion highlight the techniques used in these classes. Due to the length and overall redundancy of the classes, a single recipe listing (normally Recipe 18-1) has been omitted from this section. The examples you’ve already seen together comprise this section’s “recipe.” The sample code joins these techniques together into a group of Address Book wrappers. So Recipe 18-1 can be found in the sample code for this chapter.
The custom ABContact
class is somewhat based on the Mac-only ABPerson
class. It provides a more Cocoa Touch interface with built-in Objective-C 2.0 properties than the C-style property queries that Apple’s ABPerson
uses. All the contact-specific methods you’ve seen to date in this section derive from this class.
A second class called ABGroup
wraps all the group functionality for ABRecordRef
instances. It offers Objective-C access to group creation and management. Use this class to build new classes, and add and remove members.
The final class, ABContactsHelper
provides address book-specific methods. Use this class to search through the address book, retrieve arrays of records, and so forth. Although I have included a few basic searches across names and phone numbers, you can easily expand this class, which is hosted at http://github.com/erica, for more complex queries.
Predicate-based searches are both fast and effective. Recipe 18-2 shows predicate-based queries in action. It presents a search table (like the one introduced in Recipe 11-16) that displays a scrolling table of contacts. This table responds to user search bar queries with live updates.
To get the code used for this recipe, go to http://github.com/erica/iphone-3.0-cookbook-, or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 18 and open the project for this recipe.
Because search tables have two data sources, this recipe uses two arrays. A contacts array stores the entire address book contacts list. A second, filtered array is built each time the user updates the search bar, using the contactsMatchingName:
method you just read about in the preceding section.
When a user taps a row, this recipe displays an ABPersonViewController
instance. This class offers a view that displays the details for a given record, similar to Figure 18-1. To use this view controller, you allocate and initialize it and set its displayedPerson
property. As you’d expect, this property stores an ABRecordRef
.
Person view controllers offer a limited delegate. By setting the personViewDelegate
property, you can subscribe to the personViewController:shouldPerformDefaultActionForPerson:
method. This method triggers when users select certain items in the view, including phone numbers, e-mail addresses, URLs, and addresses. Return YES
to perform the default action (dialing, e-mailing, etc.) or NO
to skip. Recipe 18-2 uses this callback to display the value for the selected item in the debug console. Although you can interact with other display elements like the contact note and ringtone, these items do not produce callbacks.
To extend this recipe to allow editing, set the person view controller’s allowsEditing
property to YES
. This provides the edit button that appears at the top right of the display. When tapped, the edit button triggers the same editing features in the person view that you normally see in the Contacts application.
Recipe 18-3 expands on Recipe 18-2 by adding contact image thumbnails to each table cell. It does this by creating a new 45-by-45 pixel image. When image data is available, that image is rendered onto the thumbnail. When it is not, the thumbnail is left blank. Upon being drawn, the image is assigned to the cell’s imageView
. Please note that the imageView
property was introduced in the 3.0 SDK. For deployment to pre-3.0 firmware, you may use the cell’s image
property, which is now deprecated.
To get the code used for this recipe, go to http://github.com/erica/iphone-3.0-cookbook-, or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 18 and open the project for this recipe.
Figure 18-3 shows the interface for this recipe. In this screenshot, a search is in progress (matching against the letter “e”). The records that match the search each display their image thumbnail.
Notice how simple it is to create and use thumbnails in this recipe. It takes just a few lines of code to build a new image context, draw into it (for contacts with images), and save it out to a UIImage
instance.
The AddressBookUI framework offers a handy people picker controller. Browsing your entire Contacts list is just as easily accomplished as displaying an individual contact screen. Use the ABPeoplePickerNavigationController
class to present an interactive browser, as shown in Figure 18-4.
Allocate and display the controller before presenting it modally. Make sure to set the peoplePickerDelegate
property, which allows you to catch user interactions with the view.
When you declare the ABPeoplePickerNavigationControllerDelegate
protocol, your class must implement the following three methods. These methods respond to users when they tap a contact, or any of a contact’s properties, or when the user taps Cancel:
• peoplePickerNavigationController:shouldContinueAfterSelectingPerson:
—When users tap a contact, you have two choices. You can accept the person as the final selection and dismiss the modal view controller (as is done here in Recipe 18-4), or you can navigate to the individual display. To pick just the person, this method returns NO
. To continue to the individual screen, return YES
. The second argument contains the selected person, in case you want to stop after selecting any ABPerson
record.
• peoplePickerNavigationController:shouldContinueAfterSelectingPerson:property:identifier:
—This method does not get called until the user has progressed to an individual contact display screen. Then, it’s up to you whether to return control to your program (return NO
) or to continue (return YES
). You can determine which property has been tapped and to recover its value using the code from Recipe 18-2. Although this method should be optional, it is not at the time of writing this book.
• peoplePickerNavigationControllerDidCancel:
—When a user taps Cancel, you still want a chance to dismiss the modal view. This method catches the cancel event, allowing you to use it to perform the dismissal.
Recipe 18-4 presents the simplest possible people picking example. It presents the picker and waits for a user to select a contact. When the user does so, it dismisses the picker and changes the view controller title (in the navigation bar) to show the composite name of the selected person. Returning NO
from the primary callback means the property callback will never be called. You must still include it in your code as all three methods are required.
When you need users to pick a certain kind of property, such as an e-mail address, you won’t want to present users with a person’s street address or fax number. Limit the picker’s displayed properties to show just those items you want the users to select from. Figure 18-5’s picker has been limited to e-mail selection.
To make this happen, choose the displayed properties by submitting an array of property types to the controller. Set the picker’s displayedProperties
property. Recipe 18-5 offers two picking options, one for e-mail, the other for phone numbers. Although these examples use a single property for the properties array, you can choose to display any number of properties.
To get the code used for this recipe, go to http://github.com/erica/iphone-3.0-cookbook-, or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 18 and open the project for this recipe.
Allow your users to create new contacts with the ABNewPersonViewController
class. This view controller offers an editing screen that simplifies the interactive creation of a new address book entry. After allocating and initializing the view controller, start by creating a new contact and assigning it to the displayedPerson
property. If you want, you can prefill the contact’s record with properties first. Recipe 18-7, which follows this one, uses prefilling to modify already-existing contacts.
Next, assign the newPersonViewDelegate
and declare the ABNewPersonViewControllerDelegate
protocol. Delegates receive one callback, newPersonViewController:didCompleteWithNewPerson:
. This callback is sent for both selection and cancel events. Check the person
parameter to determine which case applies, as shown in Recipe 18-6.
To get the code used for this recipe, go to http://github.com/erica/iphone-3.0-cookbook-, or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 18 and open the project for this recipe.
If the user taps Done after editing the new contact, save the contact data to the address book. If you’re doing this manually rather than using the ABContactsHelper
helper class, make sure you both add the record and save the address book.
When a contact already exists with the same credentials, you need to handle the situation in some fashion. This recipe removes the existing contact to replace it with the new one, but you can also throw up an alert and ask the user how to proceed. The user might choose to keep the original or the replacement, or if you want, you can try to merge the two records somehow.
Recipe 18-7 uses the ABNewPersonViewController
class’s ability to prefill a form to modify existing contacts. The modify method starts by presenting a people picker controller. Once a user selects a contact, the application responds by using that contact information to populate the new person controller, which is then pushed onto the navigation stack.
To get the code used for this recipe, go to http://github.com/erica/iphone-3.0-cookbook-, or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 18 and open the project for this recipe.
As with Recipe 18-6, the code differentiates between cancel and done. This recipe, however, does not try to replace an existing contact. Instead, it simply adds the contact back into place. The contact’s record ID is established by the people picker, and that same record ID allows the new changes to overwrite the old.
What happens when you have some information like an e-mail address or a phone number, but you don’t have a contact to associate with it yet? The ABUnknownPersonViewController
allows you to add that information to a new or existing contact. This class exists to associate known properties with unknown contacts. It works like this.
You allocate and initialize the view controller and then create and prefill a record. Recipe 18-8 defines a record with a single e-mail address. You can add more items if you want, and each will be displayed in the view controller. Figure 18-6 (left) shows this recipe’s controller with its single e-mail. Assign the prefilled record to the displayedPerson
property.
To get the code used for this recipe, go to http://github.com/erica/iphone-3.0-cookbook-, or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 18 and open the project for this recipe.
The Create New Contact and Add to Existing Contact buttons are controlled by the allowsAddingToAddressBook
property. When a user taps on the new contact button, a form appears (see Figure 18-6, middle) that is prefilled with the properties from that record. If you disable this adding property, the buttons do not appear (see Figure 18-6, right). When you set the allowsAction
property to YES
, users can still tap on these elements to connect to an e-mail address, to phone numbers, and so forth. This provides a handy way to present a list of interactive contact information and URLs with an already-defined view controller.
The alternateName
and message
properties provide the text that fills the name and organization fields in Figure 18-6 (left). Although you can populate these fields with data via the record, the alternate options do not transfer to contacts. Therefore they provide a nice way to prompt the user without side effects.
Set the unknownPersonViewDelegate
property and declare the ABUnknownPersonViewControllerDelegate
protocol to receive the unknownPersonViewController:didResolveToPerson:
method. Called when the user taps Done, this method allows you to recover the record that the data was saved to. It also provides a place where you can pop the view controller and release it, knowing that the user has finished interaction with the dialog.
The Monster ID project consists of a collection of body part art that can be compiled together to form random pictures. It was developed by Andreas Gohr, and was inspired by a Web post by Don Park and the combinatoric critters. Built up by adding predrawn arms, legs, a body, and so forth, the resulting composite image produces a full creature.
The randomImage
method in Recipe 18-9 builds a monster image from its components. This art can be assigned directly to an address book contact or can be used as the seed for a new contact using the unknown person controller. Either way, you can use an ABContact
instance and assign an image using its image
property. If you’re not using the controller, don’t forget to save the address book after updating a record’s image.
To get the code used for this recipe, go to http://github.com/erica/iphone-3.0-cookbook-, or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 18 and open the project for this recipe.
Recipe 18-9 uses the unknown person controller approach. When the user taps an action button, it requests a Monster ID image and presents the controller, which is shown in Figure 18-7. The image appears in the Info page, and the standard create/add buttons allow users to add the generated art to a new or existing contact. Users can tap Back to return without adding the image. A new monster generates each time the sheet appears.
This chapter introduced Address Book core functionality for both the AddressBook and AddressBookUI frameworks. Here are a few parting thoughts about the frameworks you just encountered:
• Although useful, the low-level Address Book functions can prove frustrating to work with directly. The various helper classes that accompany this chapter may help make your life a little easier.
• Accessing and modifying an address book image works like any other field. Supply image data instead of strings, dates, or multivalue arrays. Don’t hesitate to use image data in your contacts applications.
• The view controllers provided by the AddressBookUI framework work seamlessly with the underlying AddressBook routines. There’s no need to roll your own GUIs for most common address book interaction tasks.
• The unknown person controller provides a really great way to store specific information (such as a company’s e-mail address, an important Web site, etc.) into a contact while allowing user discretion for where (or whether) to place that information.
18.116.14.118