Addressbookery

The iPhone makes the database underlying it’s built-in Contacts application available through a framework. This is handy, for two reasons:

  • If you want to let the user contact people from inside your app, it’s a good idea to make it easy for the user to search his address book.
  • If your app stores contact information, it’s a good idea to store it (when possible) in the main address book, which is backed up, and possibly synchronized with the address books in other applications.

The framework’s API is pretty simple, but I want to mention a few non-ovious aspects of it, as well as one bug, that I encountered while working with groups.

The Experiment

In order to familiarize myself with the Address Book framework, I set myself a simple task:

  • Create a new person record
  • Add it to a group (creating the group if necessary)

The first part was almost ridiculously easy, while the second was a little tricky.

Address Book UI

The iPhone SDK provides the Address Book UI framework, which includes a set of controllers that, as far as I can tell, would enable you to recreate the Contacts application in about half an hour. In particular, the ABNewPersonViewController class appears capable of handling all the tasks related to adding a new person to the address book. To invoke it, one might write code like this (in any method of a view controller):

 ABNewPersonViewController* npc = [[ABNewPersonViewController alloc] init];
UINavigationController* nc = [[UINavigationController alloc] initWithRootViewController:npc];
[self presentModalViewController:nc animated:YES];
[nc release];
[npc release];

Again, it’s presumed that “self” is a view controller. Also, if you want to dismiss the new person controller, once it’s outlived its usefulness, you’ll need to assign it a delegate. This, however, is all the code that’s required to add a record to the Address Book.

Creating Groups

Here’s some code to find a group with a specified name, or to create such a group if none exists. (Note that ab and group are member variables of the class of which this method is a member.)

- (void)setupGroup:(NSString*)name
{
	CFArrayRef a = ABAddressBookCopyArrayOfAllGroups(ab);
	for (CFIndex i = CFArrayGetCount(a)-1; i >= 0; i--)
	{
		ABRecordRef		g = (ABRecordRef) CFArrayGetValueAtIndex(a, i);

		CFStringRef		s = (CFStringRef) ABRecordCopyValue(g, kABGroupNameProperty);
		CFComparisonResult	r = CFStringCompare((CFStringRef)name, s, 0);
		CFRelease(s);
		
		if (r == kCFCompareEqualTo)
		{
			group = CFRetain(g);
			break;
		}
	}
	CFRelease(a);

	CFErrorRef err = nil;
	if (!group)
	{
		group = ABGroupCreate();
		ABRecordSetValue(group, kABGroupNameProperty, name, &err);
		if (!err)
		{
			ABAddressBookAddRecord(ab, group, &err);
		}
		if (!err)
		{
			ABAddressBookSave(ab, &err);
		}
	}
	if (err)
	{
		CFRelease(err);
	}
}

Membership

To assign a member to a group, you use the ABGroupAddMember function. What is not particularly clearly stated in the documentation (at least, it wasn’t Immediately Obvious To Me) is that you must also call the ABAddressBookSave function to commit your changes to the database. I’m not sure whether or not it matters which ABAddressBookRef you pass to the ABAddressBookSave function – I passed the same one I used to find/add the group record.

The following code demonstrates how a person is added to a group; this function is an implementation of an ABNewPersonViewControllerDelegate method. (Note, again, that ab and group are member variables of the class of which this method is a member.)

- (void)newPersonViewController:(ABNewPersonViewController*)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person
{
	CFErrorRef err = nil;
	if (person)
	{
		ABGroupAddMember(group, person, &err);
		if (!err)
		{
			ABAddressBookSave(ab, &err);
		}
	}
	if (err)
	{
		CFRelease(err);
	}
	
	// UI cleanup would go here ....
}

Bug

When first developing this code, I omitted the call to ABAddressBookSave and, as a consequence, my group assignments failed. I thought that a save might be necessary, so I checked with a call to ABAddressBookHasUnsavedChanges – that function returned false. That return value was incorrect. Apparently, this is a known problem. (Search for “6200638” in this file.)

Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in iPhone. Bookmark the permalink.

One Response to Addressbookery

  1. Benjamin says:

    Hi, first of all, your code was a lifesaver.. after reading your blog i had group functionality up and running in no time..

    However i later discovered that this does not work with addressbooks synced with exchange ? (or something) are you experiencing the same problem ?

    Ed: Well, glad to hear the code was helpful. I’m not familiar with the “Exchange” problem you mention. Can you give any more details? What does “not work” mean in this context? (I don’t have an MS Exchange server setup to test against.)