13. Handoff

Handoff is one of several features introduced with iOS 8 and OS X Yosemite to support Continuity. Given the proliferation of devices including desktop computers, laptop computers, iPhones, iPads, and iPod touches, Continuity is intended to help those devices work together seamlessly. Continuity includes features that leverage proximity of devices and special features that devices support, such as placing a call or sending a text message from a laptop or an iPad using a nearby iPhone, or having an Internet hotspot connection instantly available for another device that does not have a cellular Internet capability. Handoff provides the capability for an app or application to advertise what user activity it is currently performing so that the activity can be picked up and continued on another device.

The Sample App

The sample app for this chapter is called HandoffNotes, which is a basic note app built to demonstrate the basics of Handoff. The app enables a user to keep a list of notes using two different storage approaches: iCloud key-value storage and iCloud document storage. Each note includes a title, note text, and a date when the note was created. The Handoff capability will be advertised when a user is editing a note in either approach. Note that the sample app requires the device to be logged in to a valid iCloud account to work correctly.

Handoff Basics

Handoff requires two devices running either iOS 8 or higher or OS X Yosemite or higher. Each device must be logged in to the same iCloud account, and each device must support Bluetooth version 4.0 (also known as BTLE or Bluetooth Low Energy). This setup will allow iOS and OS X to perform automatic device pairing and enable Handoff.

When a user is engaging in a Handoff-capable activity on a device, the Handoff activity will be advertised to other nearby devices. For example, if a user is viewing a Web page in Safari on an iOS device and then opens her laptop nearby, OS X will show that an activity is available for Handoff in the Dock, as shown in Figure 13.1.

Image

Figure 13.1 Handoff advertisement shown in OS X Dock.

Similarly, if a user is editing a note in HandoffNotes and opens another iOS device nearby, the lock screen will show that HandoffNotes has an activity available for Handoff in the lower-left portion of the screen, by displaying the HandoffNotes app icon next to the Slide to Unlock view, as shown in Figure 13.2.

Image

Figure 13.2 Handoff advertisement shown on the iOS lock screen.

In either case, if the user taps the advertisement in the Dock in OS X, or slides up the icon on the lock screen in iOS, the target app will be displayed. In the Safari example the Web page that the user was browsing will be displayed, and in the HandoffNotes example the app will navigate to the note the user was editing, and include any in-progress updates to the note.

Handoff utilizes a simple mechanism to facilitate advertisement and continuation: NSUserActivity. In iOS, many UIKit classes (including UIResponder) have an NSUserActivity property so that a user activity can be set and propagate through the responder chain. This enables the developer to determine the best representation of a user activity, whether that is viewing some content, editing some text, or performing some other activity, and tie a Handoff activity to that directly in the user interface. In addition, UIDocument has support for NSUserActivity that makes supporting Handoff in document-based apps simple.

An instance of NSUserActivity contains two critical pieces of information: an activityType that identifies what the user is doing, and a userInfo dictionary that contains specific information about the user activity. An activityType should be a reverse DNS string identifier that uniquely identifies an activity. When a user activity is set, iOS will be able to advertise it, as shown in Figures 13.1 and 13.2. When the user continues the activity on another device, the activity will be passed to the destination app, and then the activityType and userInfo can be used by the destination device to navigate to the right place in the app and pick up the activity where it left off. Note that the userInfo dictionary is not intended to be a data transport mechanism. Apple recommends that the userInfo dictionary contain the minimum of information required to restart the user activity on another device, and that other transport mechanisms such as iCloud be used to move the actual data. NSUserActivity does provide a capability to establish a data stream between devices to move data between them if other transport mechanisms are not sufficient.


Tip

Before attempting Handoff in an app, be sure that Handoff works with test devices using Apple’s apps that support Handoff (like Safari, Keynote, or Pages, for example). This will ensure that the test devices are properly configured to meet all the Handoff requirements, like having Bluetooth 4.0–enabled and matching iCloud accounts. Without this step, it is easy to spend a lot of time investigating Handoff issues in the wrong place.


Implementing Handoff

For apps that are not document based, Handoff can be implemented directly. The developer must determine what activity or activities the app should advertise, and how the Handoff should occur. A Handoff will frequently require navigation to the right place in an app, and potentially some data transfer of in-progress updates.

To get started, the app needs to declare what activity types are supported. In the Info.plist for the target, there needs to be an entry called NSUserActivityTypes. That entry should contain an array of activity type strings, each of which represents a reverse DNS identifier for an activity type that the app supports, as shown in Figure 13.3.

Image

Figure 13.3 NSUserActivityTypes entry for manual NSUserActivity.

After the app info is configured, it can be customized to advertise a user activity and continue a user activity.

Creating the User Activity

The sample app uses the iCloud key-value store as a trivial storage and transport mechanism to illustrate the manual approach to Handoff; production apps will likely have a more complex and robust approach to storage and data transport. The sample app maintains a list of notes in an array in the key-value store, and ensures that the key-value store is consistent across devices. To communicate which note is being edited, the sample app just needs to know the index of the note in the note array. In addition, the sample app will capture in-progress updates to the note to communicate as well.

When the user is editing a note in ICFManualNoteViewController, an instance of NSUserActivity will be created and configured with an activity type. The activity type string must match the string declared in the Info.plist file. The sample app configures the user activity in the viewWillAppear: method, but other apps should take a close look at what the user is doing to determine the right timing for setting up the user activity.

NSUserActivity *noteActivity = [[NSUserActivity alloc] initWithActivityType:@"com.explore-systems.handoffnotes.manual.editing"];

noteActivity.userInfo = @{@"noteIndex":@(self.noteIndex),
                          @"noteTitle":self.note[@"title"],
                          @"noteText":self.note[@"note"]};

[noteActivity setDelegate:self];
 self.userActivity = noteActivity;

The user activity is configured with a userInfo dictionary containing information about the current activity, is assigned the current view controller as the delegate, and then is assigned to the current view controller’s userActivity property. After that is done, the app will automatically start advertising the user activity to other devices.

As the user is performing an activity, the userActivity should be kept up-to-date to correctly reflect the state of the activity. In the sample app, as the user updates the text and title of the note, the userActivity will be updated. To efficiently update the userActivity, the sample app indicates that something has happened that requires the userActivity to be updated. In this case when the user changes the text in either the note text view or the title text label, the setNeedsSave: method will be called.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

    [self.userActivity setNeedsSave:YES];
    return YES;
}
- (void)textViewDidChange:(UITextView *)textView {
    [self.userActivity setNeedsSave:YES];
}

Then, the delegate method called updateUserActivityState: can be implemented to the update the user activity.

- (void)updateUserActivityState:(NSUserActivity *)activity {
    activity.userInfo = @{@"noteIndex":@(self.noteIndex),
                          @"noteTitle":self.noteTitle.text,
                          @"noteText":self.noteDetail.text};
    NSLog(@"user info is: %@",activity.userInfo);
}

The delegate method will be called periodically and will keep the user activity up-to-date. As the user activity is advertised, it will be visible on other devices, as shown in Figure 13.2. When the user leaves the editing view, the user activity will be automatically invalided and the advertisement will stop. If a user activity should be ended independently of a view controller, the invalidate method can be called.

Continuing an Activity

When a user swipes up the icon as shown in Figure 13.2 to continue an activity, the app delegate will be notified that the user would like to continue an activity. First, the application:willContinueUserActivityWithType: method will be called to determine whether the app can continue the activity. This method should return a YES or NO to indicate whether the activity will be continued, and any custom logic required to determine whether an activity will be continued can be implemented there. In addition, this is a good opportunity to update the user interface to notify the user that an activity will be continued, in case the continuation is not instantaneous.

Next, the application:continueUserActivity:restorationHandler: method will be called to perform the continuation. This method should check the activityType of the passed-in userActivity, and do any necessary setup or navigation to get the app to the right place to continue the activity. After the setup is complete, the restorationHandler can be called with an array of view controllers that should perform additional configuration to restore the activity. In the sample app, the method will synchronize the iCloud key-value store to ensure that state is the same across devices. Then, it will navigate to the manual note list, and call the restoration handler passing the manual note list view controller to perform the rest of the navigation and setup.

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main"
                                                     bundle:[NSBundle mainBundle]];

ICFManualNoteTableViewController *manualListVC = [storyboard instantiateViewControllerWithIdentifier: @"ICFManualNoteTableViewController"];

[navController pushViewController:manualListVC animated:NO];

restorationHandler(@[manualListVC]);

When the restorationHandler block is called, it will call the restoreUserActivity-State: method on each view controller included in the array parameter. In the sample app, the method will navigate to the right note for the index included in the userInfo of the user activity.

if ([activity.userInfo objectForKey:@"noteIndex"]) {
    NSNumber *resumeNoteIndex = [[activity userInfo] objectForKey:@"noteIndex"];

    NSIndexPath *resumeIndexPath = [NSIndexPath indexPathForRow:[resumeNoteIndex integerValue]
                         inSection:0];
    [self.tableView selectRowAtIndexPath:resumeIndexPath
                                animated:NO
                          scrollPosition:UITableViewScrollPositionNone];

    [self performSegueWithIdentifier:@"showNoteDetail" sender:activity];
}

The method passes the user activity as a parameter when performing the segue to the detail screen; this enables the segue to customize the note with in-progress information.

if ([segue.identifier isEqualToString:@"showNoteDetail"]) {

    ICFManualNoteViewController *noteVC = (ICFManualNoteViewController *)segue.destinationViewController;

    NSUInteger selectedIndex = [[self.tableView indexPathForSelectedRow] row];
    NSDictionary *note = [self.noteList objectAtIndex:selectedIndex];

    if ([sender isKindOfClass:[NSUserActivity class]]) {
        NSUserActivity *activity = (NSUserActivity *)sender;
        note = @{@"title":activity.userInfo[@"noteTitle"],
                 @"note":activity.userInfo[@"noteText"],
                 @"date":note[@"date"]};
    }
    [noteVC setNote:note];
    [noteVC setNoteIndex:selectedIndex];
}

After the segue is complete, the note detail screen reflects the state of the note on the originating device. Because the originating user activity has been successfully continued, it should stop advertising to other devices. To do that, the note detail view controller implements the user-ActivityWasContinued: method, which calls the invalidate method on the userActivity so that it stops advertising from the originating device.

- (void)userActivityWasContinued:(NSUserActivity *)userActivity {
    [self.userActivity invalidate];
}

Now that the app has communicated the state of a user activity from one device to another, the user can pick up on the new device where he left off the originating device with minimal effort.

Implementing Handoff in Document-Based Apps

Instances of UIDocument have an NSUserActivity property that is automatically set up, called userActivity. If the document is saved in iCloud, the file URL of the document will be included in the userInfo of the userActivity so that the receiving device can access the document. In the sample app, the document notes demonstrate this approach. To see it in action, tap UIDocument from the top menu, and tap an existing note. When the document has been opened in the viewDidLoad method of ICFDocumentNoteViewController, an NSUserActivity is created and assigned to the document automatically, with no custom code required.

self.noteDocument =
[[ICFNoteDocument alloc] initWithFileURL:[self noteURL]];

[self.noteDocument openWithCompletionHandler:^(BOOL success) {

    [self.noteTitle setText:[self.noteDocument noteTitle]];
    [self.noteDetail setText:[self.noteDocument noteText]];

    UIDocumentState state = self.noteDocument.documentState;

    if (state == UIDocumentStateNormal) {
        [self.noteTitle becomeFirstResponder];
        NSLog(@"opened and first responder.");
    }
}];

In order for the NSUserActivity instance to be created automatically for the UIDocument instance, some additional set up in the project is required. For each document type supported by an app, an NSUbiquitousDocumentUserActivityType entry needs to be included in the CFBundleDocumentTypes entry in the Info.plist file as shown in Figure 13.4.

Image

Figure 13.4 NSUbiquitousDocumentUserActivityType entry for automatic NSUserActivity on UIDocument.

In addition, a Uniform Type Indicator (UTI) needs to be declared for the document in the Info.plist file as shown in Figure 13.5.

Image

Figure 13.5 Uniform Type Indicator (UTI) entry for UIDocument file type.

When the activity is continued on another device, the application:continueUserActivity:restorationHandler: method in the app on the receiving device will check the activity type, and then will navigate to the document note list screen and call the restorationHandler.

UINavigationController *navController = (UINavigationController *)self.window.rootViewController;

[navController popToRootViewControllerAnimated:NO];

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];

ICFDocumentNoteTableViewController *documentListVC = [storyboard instantiateViewControllerWithIdentifier: @"ICFDocumentNoteTableViewController"];

[navController pushViewController:documentListVC animated:NO];
restorationHandler(@[documentListVC]);

The restorationHandler will call the restoreUserActivityState: method in the document note table view controller, which will get the file URL for the document in the userInfo of the userActivity passed in, using the key NSUserActivityDocumentURLKey.

if ([activity.userInfo objectForKey:NSUserActivityDocumentURLKey]) {

    self.navigateToURL = [activity.userInfo objectForKey:NSUserActivityDocumentURLKey];

    [self performSegueWithIdentifier:@"showDocNoteDetail"
                              sender:activity];
}

The document note detail screen will be displayed, the document for the file URL in the activity will be loaded, and the user can pick up on the new device where she left off the originating device.

Summary

This chapter looked at using Handoff to continue a user’s activity between devices in an app. It covered the basic requirements of Handoff, such as having Bluetooth 4.0–enabled devices running either OS X Yosemite or iOS 8.0 or higher on the same iCloud account. It explained how to set up a user activity so that it will be advertised to other devices, and how to continue a user activity from another device. This chapter also explained how to support automatic user activity creation and handling with UIDocument.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.104.238