Although Objective C is an object-oriented language, not all of the frameworks you’ll use while developing for iOS are object-oriented.
Some are written in C, such as the Address Book API, which you’ll learn about in this tutorial.
The Address Book is the way you can read or modify the user’s contacts from your apps (the same contacts that show up in the Contacts app).
Because the Address Book is a C-based API, it does not use objects; instead it utilizes other types. In this Address Book tutorial, you’ll become familiar with a few of these:
- ABRecordRef: This is a contact record. It has all the properties that can be entered into the Contacts app.
- ABAddressBookRef: This is the collection of all the user’s contacts. You can modify its records, add new records, or delete a record.
- ABMutableMultiValueRef: This is the mutable version of ABMultiValueRef, though it is much more convenient. It lets you set properties of the ABRecordRef that may have multiple entries, such as phone number or email.
This Address Book tutorial assumes you are familiar with the basics of iOS development, and are comfortable with the basics of C syntax. If you are new to either of these, check out some of the other tutorials on our site.
Alright, let’s dive in and get some addresses!
Getting Started
To begin this tutorial, download the starter project that has the user interface pre-made, so you can stay focused on the Address Book part of the tutorial.
Build and run, and get ready to meet our furry little friends: Cheesy, Freckles, Maxi, and Shippo!
Open up ViewController.m. The starter app has four UIButtons that all call petTapped:
when pressed. If you look closely in the Utilities/Attributes Inspector in Main.storyboard, notice that each UIButton has a different number in its tag. This will help you distinguish which button is the one that called petTapped:
.
To use the Address Book API, you need to import the framework and headers. As of iOS 7, this is easy with the @import
keyword. Add this line to the top of ViewController.m:
@import AddressBook; |
In this app, the user will press the image of one of the pets, and the pet’s contact information will be added to their address book (I’m surprised that even pets have iPhones!). Using the power of the Address Book API, you can finally contact your favorite furry friends.
Asking for Permission
In 2012, there was a controversy regarding certain apps sending a copy of the user’s Address Book to its servers. Because of the public’s response to this security breach, Apple immediately implemented security features to prevent this from happening without the user’s knowledge.
So now, whenever you want to use the Address Book, you first ask the user for permission.
Let’s try this out. In ViewController.m, add the following code inside petTapped:
- (IBAction)petTapped:(UIButton *)sender { if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusDenied || ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusRestricted){ //1 NSLog(@"Denied"); } else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized){ //2 NSLog(@"Authorized"); } else{ //ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined //3 NSLog(@"Not determined"); } } |
Let’s go over this section by section:
- This checks to see if the user has either denied your app access to the Address Book in the past, or it is restricted because of parental controls. In this case, all you can do is inform the user that you can’t add the contact because the app does not have permission.
- This checks to see if the user has already given your app permission to use their Address Book. In this case, you are free to modify the Address Book however you want.
- This checks to see if the user hasn’t decided yet whether not to give permission to your app.
Build and run, and tap the cutest pet. You should see something like the following in the console:
PetBook[832:70b] Not determined
In real life when you want something, you ask. Same thing here!
So you need to request the user for access to the address book. Insert the following in section 3:
ABAddressBookRequestAccessWithCompletion(ABAddressBookCreateWithOptions(NULL, nil), ^(bool granted, CFErrorRef error) { if (!granted){ //4 NSLog(@"Just denied"); return; } //5 NSLog(@"Just authorized"); }); |
The first parameter of this function is an ABAddressBookRef
, which you create with ABAddressBookCreateWithOptions(NULL, nil)
. The second argument is a block that executes once the user responds to your request.
Section 4 executes only if the user denies permission when your app asks. This should be treated the same way as when the ABAuthorizationStatus() == kABAuthorizationStatusDenied
.
Section 5 executes if the user gives permission for you to use the Address Book. This is the same as kABAuthorizationStatusAuthorized
.
Run the app in the simulator and tap on the weirdest looking pet. A popup will appear to request access to the Address Book:
Depending on your choice, you’ll see either “Just authorized” or “Just denied” in the console. Now, press an image again, and you’ll see the result is related to your action before: if you gave permission, it will say “Authorized”, or else it will say “Denied”.
Remove the NSLogs, they were only there so you could see the code’s behavior.
Note: To debug after each test, it might be useful to use iOS Simulator/Reset Content and Settings. This will let you see the alert that asks for permission every time you reset the settings, which will help later on.
Next, add this code into sections 1 and 4 to tell the user that you can’t add the contact because it does not have needed permissions:
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]; |
Use iOS Simulator/Reset Content and Settings to reset your simulator, build and run, tap the ugliest pet, deny permission, and verify the dialog appears. Good job, iOS citizen!
Creating the Pet’s Record
Now it’s time to move onto actually creating the records. Underneath petTapped:, create a new method called addPetToContacts:
- (void)addPetToContacts: (UIButton *) petButton{ } |
In this method, you’ll create an ABRecordRef
with the pet’s attributes, check the address book to make sure the pet does not already exist, and if the pet is not in the Address Book, add it to the user’s contacts.
Begin addPetToContacts:
with the following.
NSString *petFirstName; NSString *petLastName; NSString *petPhoneNumber; NSData *petImageData; if (petButton.tag == 1){ petFirstName = @"Cheesy"; petLastName = @"Cat"; petPhoneNumber = @"2015552398"; petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Cheesy.jpg"], 0.7f); } else if (petButton.tag == 2){ petFirstName = @"Freckles"; petLastName = @"Dog"; petPhoneNumber = @"3331560987"; petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Freckles.jpg"], 0.7f); } else if (petButton.tag == 3){ petFirstName = @"Maxi"; petLastName = @"Dog"; petPhoneNumber = @"5438880123"; petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Maxi.jpg"], 0.7f); } else if (petButton.tag == 4){ petFirstName = @"Shippo"; petLastName = @"Dog"; petPhoneNumber = @"7124779070"; petImageData = UIImageJPEGRepresentation([UIImage imageNamed:@"contact_Shippo.jpg"], 0.7f); } |
By looking at the button the user chose, you can determine which pet was selected. If the user pressed Shippo, then you want the user to have Shippo’s contact information. The only thing that may be unfamiliar here is UIImageJPEGRepresentation()
, which takes a UIImage and returns an NSData representation of it.
Next, type this at the end of addPetToContacts:
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, nil); ABRecordRef pet = ABPersonCreate(); |
The first line creates the ABAddressBookRef
that will add the pet to the user’s contacts later. The second line creates an empty record for your app to fill with the pet’s information.
Next, set the pet’s first and last names. This code will look like this.
ABRecordSetValue(pet, kABPersonFirstNameProperty, (__bridge CFStringRef)petFirstName, nil); ABRecordSetValue(pet, kABPersonLastNameProperty, (__bridge CFStringRef)petLastName, nil); |
A quick explanation:
ABRecordSetValue()
takes anABRecordRef
as its first parameter, and that record ispet
.- The second parameter calls for an
ABPropertyID
, which is a value defined by the API. Because you want to set the first name, you passkABPersonFirstNameProperty
. - For the last name, similarly pass
kABPersonLastNameProperty
.
Does the third argument seem confusing? What it does is take a CFTypeRef
, which is the broad type that includes CFStringRef
and ABMultiValueRef
. You want to pass a CFStringRef
, but you only have an NSString
!
To convert an NSString
to a CFStringRef
, you have to bridge it using (__bridge CFStringRef) myString;
. If you are familiar with “casting the variable,” it is similar to that.
Note: For more information on the __bridge
keyword, check out Chapters 2 and 3 in iOS 5 by Tutorials, Beginning and Intermediate ARC.
Phone numbers are a bit trickier. Since one contact can have multiple phone numbers (home, mobile, etc.), you have to use ABMutableMultiValueRef
. This can be done by adding the following code to the end of addPetToContacts:
.
ABMutableMultiValueRef phoneNumbers = ABMultiValueCreateMutable(kABPersonPhoneProperty); ABMultiValueAddValueAndLabel(phoneNumbers, (__bridge CFStringRef)petPhoneNumber, kABPersonPhoneMainLabel, NULL); |
When you declare the ABMutableMultiValueRef
, you have to say what kind of property it will. In this case, you want it to be for the kABPersonPhoneProperty
. The second line adds the pet’s phone number (which is bridged to CFTypeRef
), and you have to give this phone number a label. The label kABPersonPhoneMainLabel
says that this is the contact’s primary number.
Try setting the pet’s phone property yourself. If you get stuck, expand the field below!
The last piece of information to add to the record is its picture — you definitely want to see that adorable face when they call to ask for treats!
To set the record’s image, you use this statement:
ABPersonSetImageData(pet, (__bridge CFDataRef)petImageData, nil); |
To add this contact and save the address book, use the following two short lines:
ABAddressBookAddRecord(addressBookRef, pet, nil); ABAddressBookSave(addressBookRef, nil); |
As a final step, you need to call this new method in the appropriate spots. So add this line of code in sections 2 and 5 inside petTapped::
[self addPetToContacts:sender]; |
Use iOS Simulator/Reset Content and Settings to reset your simulator, build and run, and tap on each of the pets. If asked, give the app permission to use the Address Book.
Once you’re done, go to the home screen (use Cmd+Shift+H to do this in the simulator), and go to the Contacts app. You should see the pets!
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.
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!
Address Book Tutorial in iOS is a post from: Ray Wenderlich
The post Address Book Tutorial in iOS appeared first on Ray Wenderlich.