Address Book Tutorial in iOS
In this Address Book Tutorial in iOS, learn how to add and edit contacts in a fun app about pets. By Evan Dekhayser.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Address Book Tutorial in iOS
20 mins
Duplicates? No More!
There are still a few things to fix up. First, you may have noticed that if you tap a pet twice, two entries will appear in the contact list. But there can only be one Shippo! ;]
To prevent Shippo clones, you should iterate through all the contacts and make sure that the new contact’s name is not in the address book already.
Insert the following sections of code before the ABAddressBookAddRecord()
and ABAddressBookSave
calls.
First, add this line to get an NSArray
of the Address Book’s contacts.
NSArray *allContacts = (__bridge NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBookRef);
Notice how you use __bridge
going to NSArray in this case. It goes both between Core Foundation –> Foundation and Foundation –> Core Foundation.
Next, add this line to iterate through the array so that you can check the name of every record.
for (id record in allContacts){
ABRecordRef thisContact = (__bridge ABRecordRef)record;
if (CFStringCompare(ABRecordCopyCompositeName(thisContact),
ABRecordCopyCompositeName(pet), 0) == kCFCompareEqualTo){
//The contact already exists!
}
}
You have to use id
because technically Core Foundation types can’t be in an NSArray, because they are not objects. The ABRecordRefs are disguised as id
’s to avoid errors. To get the ABRecordRef, simply __bridge
again!
The way you use CFStringCompare
here is similar to using NSString’s isEqualToString:
ABRecordCopyCompositeName
gets the full name of the record by joining the contact’s first and last names.
Finally, add the following to the if statement:
UIAlertView *contactExistsAlert = [[UIAlertView alloc]initWithTitle:[NSString stringWithFormat:@"There can only be one %@", petFirstName] message:nil delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
[contactExistsAlert show];
return;
This shows an alert to the user and stops the method so the contact isn’t added. If the loop goes through all the contacts and does not find a match, it adds the pet to the Address Book.
Run the app, and try to select one of the pets multiple times. Look for an alert that says the contact already exists to appear.
It’s also helpful if you add an alert whenever the contact is added. At the end of this method, after the app adds and saves the contact, insert this confirmation alert:
UIAlertView *contactAddedAlert = [[UIAlertView alloc]initWithTitle:@"Contact Added" message:nil delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil];
[contactAddedAlert show];
Multithreading
There is still a hidden issue with the code as it is right now. If you look at the documentation for ABAddressBookRequestAccessWithCompletion
(which is called in petTapped:
) the completion is called on an arbitrary queue. In other words, it may be called on a thread besides the main thread.
If there is one thing you should know about multithreading, it’s that the user interface can only be used on the main thread. You have to make sure that anything affecting the user interface (presenting UIAlertView?) is called on the main thread.
This is easy to accomplish with the following code. Insert this at the beginning of the completion of ABAddressBookRequestAccessWithCompletion
.
dispatch_async(dispatch_get_main_queue(), ^{
});
This runs the block on the main thread so you can use the user interface. To learn more about multithreading, read this tutorial.
Cut and paste the code from inside the completion handler into the dispatch_async block, to make the call look like this:
ABAddressBookRequestAccessWithCompletion(ABAddressBookCreateWithOptions(NULL, nil), ^(bool granted, CFErrorRef error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!granted){
//4
UIAlertView *cantAddContactAlert = [[UIAlertView alloc] initWithTitle: @"Cannot Add Contact" message: @"You must give the app permission to add the contact first." delegate:nil cancelButtonTitle: @"OK" otherButtonTitles: nil];
[cantAddContactAlert show];
return;
}
//5
[self addPetToContacts:sender];
});
});
This is the best way for the app to ask permission to use the Address Book. Note that a best practice is to ask for permission only when you actually need to use it — if you ask for permission when the app first launches, the user may be suspicious because they won’t understand why you need to use the Address Book.
One issue with ABAddressBookRequestAccessWithCompletion
is that once the user gives the app permission, sometimes there is a 5-10 second delay until the completion is called. This can make it seem like the app’s frozen when it’s adding the contact. In most cases, this is not too much of an issue.
Your PetBook app is now fully functional, and I know you can’t wait to text your new furry friends right away!
Note: Sorry to disappoint all the raving Shippo fans, but the numbers listed here are fake :]
Where To Go From Here?
Here is the finished example project from this Address Book tutorial.
You can do many other cool things with the Address Book API. In this tutorial, you learned how to create a new record. As an extension of this tutorial, try modifying pre-existing contacts in the Address Book.
Along with this framework, there is also an AddressBookUI.framework
that has several convenient classes for modifying the address book. What these can do is give your app functionality similar to the contacts app.
If you have any questions or comments regarding this tutorial or the Address Book API, please join the discussion in the comments below!