Working with the AddressBook API for Contacts - Using the APIs - Learning iCloud Data Management (2014)

Learning iCloud Data Management (2014)

Part II: Using the APIs

4. Working with the AddressBook API for Contacts

Now that you have a barebones app in place, it’s time to add various iCloud features to it. The chapters in this part of the book focus on the user data that can be synchronized across the user’s devices (subject to settings on the various devices, of course) using iCloud. Some of that data is entered explicitly either with the keyboard, with Siri or dictation, or with options in Mail and other automated hooks. The data discussed in this chapter—AddressBook data—can be entered explicitly, but it also frequently is added as a result of your normal actions, such as addressing an email message. This chapter shows you how to access that data as well as how to update AddressBook data.

Considering the AddressBook API on iOS and OS X

OS X and iOS both implement the AddressBook API, and you can synchronize your data through iCloud very easily. However, as you will see, the programmatic API for generating email messages is different on the two operating systems. Just as with bindings (implemented on OS X but not on iOS), the generation of email messages uses different tools—AppleScript on OS X and the MessageUI framework on iOS (AppleScript does not exist on iOS).

Also, as has been pointed out previously, don’t worry about the distinction between AddressBook and Contacts. AddressBook is the API and was originally the name of the app that provided a user interface on OS X. The apps have been renamed now (Contacts on both operating systems), but the API retains is original title.

The ability to quickly send an email message to someone in your address book (be it in AddressBook or Contacts) is a very basic part of any computer that supports email. For that reason, it’s available on both iOS and OS X, albeit in different implementations. It’s another aspect of the operating system that “just works.” On iOS, the “just working” part is courtesy of MFMailComposeViewController and its framework. As you’ll see, you can type in a message, tap a button, and send it on its way. What you don’t see in this chapter is that when you’re addressing that email message, as you type names, type-ahead logic shows you possible addresses. To accomplish this, you as a developer do nothing more than use the standard compose sheet for email messages (it’s described in this chapter).

On OS X, you build the functionality of MFMailComposeViewController into your code. There are several reasons for this. Perhaps the most important one is that on OS X, there never was a framework such as MFMailComposeViewController. Over the years (decades, in fact), developers have built and shared interfaces to interact with AddressBook. You can find them in sample code on developer.apple.com as well as in various places on the web if you search for them. There was no such resource on iOS at the beginning, so Apple created one very early on.

Another issue to think about with regard to the interface is that, in real-world deployments, the entry and editing of AddressBook data is often done on mobile devices on an as-needed basis (or, rather, on a meeting-someone-and-getting-their-contact-info basis). Much more commonly, contact data is synchronized from WebDAV or Exchange servers where an organization and its IT department manage the data. To be sure, there are many, many environments where contact information is managed from a desktop or laptop computer, but the mobile and shared server configurations are very strong players in the field of contact management.

Sending Mail from the iOS App

The MessageComposer sample app (downloadable from developer.apple.com) gives you a firm basis for implementing in-app email. It relies on the MFMailComposeView-Controller class that was introduced in iOS 3.0. It provides a view controller interface to Mail along with the necessary methods (very few, as you will find out). In fact, your basic do-nothing shell of an app is ready to send email with just a few additions. Figure 4.1 shows the interface as it appears on iPhone 5.

Image

Figure 4.1 Sending email from your app

As is often the case with Cocoa and Cocoa Touch, your implementation of functionality consists of a number of adjustments to settings and wiring up of components old and new.

Note that MFMailComposeViewController takes care of sending the email once the user taps Send. If you are testing your code on the simulator, you can’t send email. Thus, for testing, you will have to provision your app for distribution (actually ad hoc distribution), as described indeveloper.app.com. If you are using a development provisioning profile, you can’t send email. You can pose the sheet on the simulator, but all you can do is tap Cancel. Furthermore, you may notice that the message field doesn’t contain all of the text you expect it to (this is particularly true if your message is lengthy). Before you panic, use a distribution profile and test it on an actual device.

Making Sure You Can Send Mail

The code in this chapter builds on the code from the end of Chapter 3, “Introducing the APIs and the First Apps.” You need to perform two tests:

Image Make certain that Mail is configured on the iOS device.

Image Make certain that you have an Internet connection.

The sequence of these tests is important. If Mail is not configured, you can forget about sending email. All is not lost, though, because you can advise the user to configure Mail and then continue with your app. If Mail is configured but there is no Internet connection, you can warn the user, but there is no reason to stop. The message can be configured, and Mail can put it in its queue to be sent. When there is an Internet connection, the message will automatically be sent.

Most people use a variation on code from the Reachability sample code on developer.apple.com to perform these tests. Here are the steps to take to modify the code from Chapter 3:

1. Download Reachability. As is always the case with downloading sample code, it makes sense to quickly run it to make certain that all the pieces are present. Look at the date of last modification and read the comments about the version you have downloaded.

2. Open the project and locate Reachability.h and Reachability.m. Add them to your app’s target. You can do this either by dragging them into the navigator or by selecting your project in the navigator and using File, Add Files. (The right mouse button also brings up this command as one of the commands in the shortcut menu.)

3. Select your project in the navigator, and then click the General tab. In Linked Frameworks and Libraries, use + to add SystemConfiguration.framework. This is needed by Reachability.

4. Still in Linked Frameworks and Libraries, add MessageUI.framework.

Sending the Message

With this preparation, you’re ready to move the text from the text view into a new email message. This section shows you the code for that process: the checks for Internet and email will come in the next sections.

Expanding the tapButton Method

You’ll expand on the tapButton method that you have created in ViewController. Add the declaration of a new method to ViewController.h, as shown in Listing 4.1. This message will use MFMailComposeViewController to send the message. As you will see, you need to provide a delegate that conforms to MFMailComposeView-ControllerDelegate to handle the send result.

Listing 4.1 Adding a New Method to View Controller


#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
@property (weak, nonatomic)
IBOutlet UITextView *userTextView;

- (IBAction)tapButton:(id)sender;

- (void)displayComposerSheet; //1

@end


1 This is the declaration of the new method.

Importing the Frameworks to ViewController.h

You’ll need to add new files to ViewController.m. Listing 4.2 shows what the top of that file will look like now.

Listing 4.2 Importing a New File to ViewController.m


#import "ViewController.h" //1
#import "Reachability.h" //2

#import <MessageUI/MessageUI.h> //3
#import <MessageUI/MFMailComposeViewController.h> //4

@interface ViewController () //5

@end //6

@implementation ViewController //7


1 This is the only line in the original file.

2, 3, 4 These are the added lines at the top of the file.

5, 6 This is the empty class extension that is automatically added to .m files beginning in Xcode 4.4. Use it to declare properties and methods.

7 This is the beginning of the implementation: there are many more lines following this one in the code that you have, but they don’t need to be changed at this point. In Listing 4.4, you’ll add another method at the bottom of the file.

Adopting the MFMailComposeViewControllerDelegate Protocol

You’ll be setting ViewController to be the MFMailComposeViewController-Delegate delegate. So the interface in ViewController.h should look like Listing 4.3 at this point.

Listing 4.3 Adding MFMailComposeViewControllerDelegate Protocol


#import <UIKit/UIKit.h> //1
#import <MessageUI/MessageUI.h> //2
#import <MessageUI/MFMailComposeViewController.h> //3

@interface ViewController : UIViewController
<MFMailComposeViewControllerDelegate> //4
@property (weak, nonatomic) IBOutlet UITextView
*userTextView;

- (IBAction)tapButton:(id)sender;

- (void)displayComposerSheet; //5

@end


1, 2, 3 This imports the framework.

4 This adds the delegate protocol to your view controller.

5 This is the method you will add later in this chapter.

Implementing the Protocol for MFMailComposeViewControllerDelegate

For readability, it’s often a good idea to bracket delegate protocol code with a pragma, as shown in Listing 4.4. Despite the spacing, this consists of a single line for the method declaration and a single line of implementation.

Listing 4.4 Using Pragmas to Bracket Protocol Code


#pragma mark MFMailComposeViewControllerDelegate

-(void)mailComposeController:
(MFMailComposeViewController*)controller
didFinishWithResult:
(MFMailComposeResult)result
error:(NSError*)error
{
[self dismissViewControllerAnimated:YES completion:nil];
}


Calling the New Method from tapButton

Now, call the new method from tapButton, which you created in Chapter 3, and provide the definition of the new method, as shown in Listing 4.5. Note that this is just the shell of the new method: for it to function properly, you’ll have to add the code shown in the following sections. However, this preliminary code is provided to give you an idea of where you are going.

Listing 4.5 Defining tapButton


- (IBAction)tapButton:(id)sender {
NSLog (@" %@", _userTextView.text);

[self displayComposerSheet]; //1

}

-(void)displayComposerSheet {
MFMailComposeViewController *composer = //2
[[MFMailComposeViewController alloc] init]; //3

composer.mailComposeDelegate = self; //4
[composer setSubject: @"Test Subject"]; //5
[composer setMessageBody: userTextView.text
isHTML: NO]; //6

[self presentViewController: composer //7
animated: YES completion:NULL];
}


1 This is the call to the new method. After the text from the text view is written to the log, it will display a sheet to let you create the email message.

2, 3 This creates an instance of MFMailViewController.

4 This sets the delegate to this view controller.

5 This sets the message subject.

6 This sets the message body to the same text you wrote to the log.

7 This presents the new view controller. This is a method of UIViewController. It needs no completion handler.

Checking That Mail Is Configured and the Internet Is Available

Listing 4.6 shows the code that performs both checks. You can replace the direct call to displayComposerSheet in tapButton with this code, which incorporates the necessary checks before it either calls displayComposerSheet or posts an alert. This code is based on the MessageComposer sample app.

Listing 4.6 Checking That Mail Is Configured and Available


- (IBAction)tapButton:(id)sender { //1
NSLog (@" %@", _userTextView.text);

Class mailClass = (NSClassFromString(@"MFMailComposeViewController"));
if (mailClass != nil) //2
{
// We must always check whether the current device is configured for
// sending emails
if ([mailClass canSendMail])
{
NetworkStatus networkStatus = [[Reachability
reachabilityForInternetConnection] currentReachabilityStatus];
if (networkStatus == NotReachable) {
[self showCantSendMailAlert]; //3
} else {
[self displayComposerSheet]; //4
}
}
else
{
[self showCantSendMailAlert]; //5
}
}
else
{
[self showCantSendMailAlert]; //6
}

}


1 tapButton was created in Chapter 3 and should already be in ViewController.m.

2 This checks that you have a mailClass class.

3, 5, 6 These display alerts for the absence of the mailClass class, no email configuration, or no network connection.

4 This composes and sends the message.

In Listing 4.7, you see the code to show the error alert as well as the code to actually display and send the email.

Listing 4.7 Sending the Message or Showing the Alert


-(void)displayComposerSheet {
MFMailComposeViewController *composer =
[[MFMailComposeViewController alloc] init]; //7

composer.mailComposeDelegate = self;

[composer setSubject: @"Test Subject"]; //8

[composer setMessageBody: userTextView.text isHTML: NO]; //9

[self presentViewController: composer
animated: YES completion:NULL]; //10
}

-(void)showCantSendMailAlert //11
{

UIAlertView *alert = [[UIAlertView alloc]
initWithTitle: @"Email Not Configured"
message: @"You can continue with the message
but you will not be able to email it."
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
}


7 This allocates the compose for the message.

8 This sets the subject.

9 This sets the message body to the text the user has typed in.

10 This presents the composer to the user.

11 This is the alert to display when an error occurs. You could parameterize it with alternate wording for the different errors. Alternatively, you might log specifics to the console from this method.

Sending Mail from the OS X App

OS X does not have a class that’s comparable to MFMailComposeViewController that combines the sending of mail with the interface of AddressBook. In part, that’s because AddressBook is integrated so tightly into iOS. On OS X, the integration of AddressBook with sending email is something that you implement at the app level.

The SBSendEmail sample app on developer.apple.com lets you send email to an address that your user types in, whereas on iOS devices, the address field (like the copy fields) has that AddressBook integration with type-ahead. On a Mac, the interface and the functionality are different, and, once again, much of that difference is attributable to the presence of multiple windows on the Mac.

Sending Mail from the OS X app is implemented based on SBSendEmail; because it’s not directly an iCloud function, you should explore SBSendEmail on your own. Its integration with AddressBook is explored in the following section.

Using Property Lists for Storing and Syncing

You rarely need to look inside the AddressBook API: you usually use accessors and even higher-level methods. However, it is a good idea to know the basic outline of what’s going on—if only because it’s the same basic design pattern you’ll find with events and reminders as well as a number of other classes throughout Cocoa (and other systems). It’s a design pattern (architecture, if you prefer) that you may have already used yourself. If you haven’t already used it, you may want to consider using it. From the perspective of iCloud, this design pattern helps to build a robust and easily synchronized data structure.

If you’ve used AddressBook, you have seen that you can manage people and groups. As is common in the digital world, groups (like albums and playlists) are not places. This means that a person (or photo or song) can be in several groups at the same time. The AddressBook API implements this with two classes: ABPerson and ABGroup. Both are subclasses of the ABRecord abstract class. Because it’s an abstract class, you never directly instantiate an ABRecord object. However, because it’s the superclass of ABPerson and ABGroup, every time you instantiate an object of one of those classes, it inherits from ABRecord.

When AddressBook (or any other API) needs to access group or person information, it uses the appropriate class—ABPerson or ABGroup. However, when anything needs to access both types of instantiated classes, it can use the abstract superclass. “Syncing your address book/contacts” means syncing both of the instantiated subclasses, so it’s not hard to guess that the actual sync process syncs ABRecord objects. As noted, this design pattern reappears over and over in Cocoa, and it’s key to efficient syncing for iCloud.

If you look inside the ABRecord Class Reference on developer.apple.com, you’ll see that it’s very simple. Each instance is associated with a specific address book (by default, the shared address book), and each instance has a unique identifier (uniqueID). In addition, each instance can be marked as read-only (or not).

Then, you’ll notice a set of four methods that manipulate properties within an instance of ABRecord:

- (id)valueForProperty:(NSString *)property
- (BOOL)setValue:(id)value forProperty:(NSString *)property
- (BOOL)setValue:(id)value forProperty:(NSString *)property error:(NSError **) error
- (BOOL)removeValueForProperty:(NSString *)property

Deep down, the data for properties is stored in a property list, and these four methods let you manipulate it. Property lists are well suited to storing the properties for an object because you get and set those properties using the key strings in the property list. If the property list has keys that you do not care about (or for which you do not know the key string), you never see them. And if you add a property to a property list, no one else sees it. This is how apps can add properties to the address book without breaking things. The lists of key strings are in “Address Book Programming Guide for Mac” on developer.apple.com.

Both the use of the abstract class and the use of flexible property lists to store data for the abstract class instances are important designs to remember as you build your own classes to easily sync with iCloud.

Chapter Summary

This chapter took your first app and added iCloud data to it in the form of address book contacts. This is user data—data that the user has entered (or obtains through synchronization with WebDAV or Exchange). On iOS, users can manipulate the data with apps that use theMFMailComposeViewController framework, which you’ve seen how to use. Both OS X and iOS use the same AddressBook API, but on OS X, you need to build your own updating interface to the data.

You’ve also seen the overall structure of the AddressBook API. It involves an abstract superclass that supports ABPerson and ABGroup classes, which you instantiate. It is this abstract superclass that iCloud syncs, and you’ll see this design pattern in use through iCloud synchronizations. (In addition, you’ll probably use the structure yourself.)

Exercises

1. To properly test your AddressBook code, create a new Apple ID for which you can create your own data at https://appleid.apple.com/cgi-bin/WebObjects/MyAppleId.woa/ (there is no charge), then assign it to a separate user account in Users & Groups on OS X. This will create a new address book for this new Apple ID. In order to test iCloud syncing, assign this Apple ID to an iOS device. (Most developers don’t assign this ID to their own iPhone. Instead, find an iPhone that is not important to you. Do be careful, because if you use an older iPhone that can’t run iOS 7 or later, you may not have a current version of iCloud.)

2. In order to understand how property lists can work for flexible data, add some new properties to your address book contacts. Use the test account you created in the first exercise.

3. In this chapter, you’ve seen how to modify the iPhone version of your app to use the AddressBook API. Modify the app for iPad. The changes should be in the storyboard: your code should not need changing.