Editor’s note: This tutorial requires a paired Apple Watch and iPhone. No simulators here.
You often hear Apple fans express their admiration for the Apple ecosystem with a single phrase: “It just works.” Handoff is a new and exciting technology that aims to continue to provide an immersive and seamless “it just works” experience when using Apple devices. Apple describes Handoff this way: In iOS 8 and OS X Yosemite, Handoff lets users start an activity on one device and seamlessly resume the activity on another device. Provide continuity for users with multiple devices by supporting Handoff in your apps and websites..
Add Watch OS (the official name of the Apple Watch operating system) to the list of Apple’s products that are capable of utilizing Handoff. To see it in action, open the Messages application on your Apple Watch and navigate to a conversation. Activate the Lock Screen on your iPhone and notice the frosted Messages icon in the lower left corner. If you swipe up from the frosted Messages icon and unlock your phone iOS will launch Messages and go directly to the conversation you were viewing on the Apple Watch.
In this tutorial we’ll go through implementing a basic handoff from an Apple Watch app to the iPhone. Our application, Airports!, presents a list of major airports. Selecting the airport will bring up a MapKit-driven map of the airport. The Apple Watch companion application provides the same functionality.
As with Messages, the Airports! UI on the Apple Watch is limited. With Messages you are limited to selecting canned responses or relying on Siri. With Airports! the map is static and doesn’t provide zoom or pan functionality. With Handoff, however, we can provide a (mostly) seamless transition from looking at the map on the watch and looking at the map on an iPhone.
Starter Application
To get started, download the Airports! starter application. Open the handoffexample project and select the Airports! scheme. Prior to implementing Handoff you can run the basic application with the iPhone and/or Apple Watch simulator. However, to test Handoff itself you will need a paired Apple Watch and iPhone.
Implementing Handoff
There’s a distinct set of steps to go through to get your application “Handoff enabled.” Let’s take a look at each of the following:
- Defining user activities
- Updating
Info.plist
- Activating or deactivating Handoff notification broadcast on the Apple Watch
- Handling Handoff on the iPhone application
Handoff is based around the concept of allowing the user to seamlessly resume an activity started on one device on another. For example, composing an e-mail is an activity. Reading a web page is an activity. Interacting with a specific airport map is an activity. When thinking about adding Handoff support to your application you should first start with enumerating the list of activities you want to allow your user to resume on a target device.
Once you’ve identified the activities you should transform them into a list of NSUserActivity
activity types.
Activity | NSUserActivity Activity Type |
---|---|
Viewing a Map | it.iachieved.handoffexample.viewing.map |
The assigned NSUserActivity
activity type will be added to the Info.plist
of the applications that support the activities. An application declares support for a given set of activities by enumerating each of its NSUserActivity
identifiers in the NSUserActivities
array in the Info.plist
:
For Handoff support between the Apple Watch and iPhone, will only need to do this in the Info.plist
of the iPhone application. This is because the Handoff direction between Apple Watch and iPhone is one-way; while you can resume what you are doing on Apple Watch on the iPhone, the reverse is not true.
Before continuing complete this step by opening the Info.plist
under handoffexample/Supporting Files
and create an array named NSUserActivityTypes
and add one element of type String
with the value it.iachieved.handoffexample.viewing.map
.
Broadcasting the Handoff Activity (or Lack Thereof)
When you first launch your application on the Apple Watch the initial WKInterfaceController
awakeWithContext
method will be called. At this point I recommend adding a call to invalidateUserActivity()
. For some reason in my testing I’ve found that without this call a Handoff activity is broadcasted prematurely causing the iPhone to display the Handoff icon before an activity is actually available.
Add the following line to the willAwakeWithContext
method in InterfaceController.swift
, right after the call to super.willAwakeWithContext(context)
call:
1 |
invalidateUserActivity() |
Now, when a user presses on an airport in the list we transition to the Apple Watch map controller. At this transition point we want to broadcast (via Handoff) that a map viewing activity is taking place on the watch. We use the method updateUserActivity
to accomplish this as follows:
1 2 3 |
updateUserActivity("it.iachieved.handoffexample.viewing.map", userInfo:["iata":self.airport!["iata"]!], webpageURL:nil) |
There are a couple of things to note here:
- the
updateUserActivity
is a method of the baseWKInterfaceController
class - We are not creating an
NSUserActivity
instance (the method used in the Handoff Programming Guide) - We supply
it.iachieved.handoffexample.viewing.map
as our activity name - The
userInfo
dictionary provides information as to which airport we are viewing
Let’s look at these in turn.
First, if you start with the Handoff Programming Guide from Apple you will not find any mention of the updateUserActivity
method, but instead are given an introduction to using NSUserActivity
. Using NSUserActivity
routines in the Apple Watch extension don’t work (or rather, I couldn’t use them); you need to use updateUserActivity
, a method of the WKInterfaceController
instead.
Second, we supply our activity name it.iachieved.handoffexample.viewing.map
. It bears repeating that this must align with the value we declared as a supported activity in the Info.plist
for the iPhone application.
Finally, our iPhone application needs to know which airport we are viewing on the Apple Watch. We provide that information by supplying a userInfo
dictionary with a key-value pair of the IATA airport code we are viewing (which is available via the Airports
dictionary). Note that what we are providing here is arbitrary; we could have just as easily provided the index location in the Airports
array if that value was readily available in the WatchMapController
class. Since we had an airport dictionary (which contained the iata
key), we used that instead.
Before continuing let’s add the call to our WatchMapController
willActivate
method as follows:
1 2 3 4 5 6 7 8 9 |
override func willActivate() { super.willActivate() updateUserActivity("it.iachieved.handoffexample.viewing.map", userInfo:["iata":self.airport!["iata"]!], webpageURL:nil) } |
Once we call updateUserActivity
the Handoff routines built into Watch OS will broadcast this information (via BTLE) and the paired iPhone will display a Handoff icon for the Airports! app. Swiping up on the icon and unlocking the phone will launch the iOS Airports! application to the initial view controller (the table view). This is a good start, but we want it to go to the same map we were viewing on the watch. Let’s add the plumbing in the iOS application to be able to take action with the Handoff notification and data.
Handling Our Handoff Notification
We’re interested in implementing one method to add to the AppDelegate
of our iOS application. It can be easy to forget that the AppDelegate
is in large part a collection of application entry methods that iOS will call. Since Handoff is a part of the OS, it stands to reason that our initial interaction with Handoff will be a call to an application delegate method. In this case we want to implement continueUserActivity
. Here is our implementation:
1 2 3 4 5 6 7 8 |
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]!) -> Void) -> Bool { if let window = self.window, rvc = window.rootViewController { rvc.childViewControllers.first?.restoreUserActivityState(userActivity) } return true } |
Go ahead and add the above to the iPhone AppDelegate
code.
Let’s take a look at our implementation of continueUserActivity
. First, we want to make sure that we have a non-nil root view controller, because we will be making use of its childViewControllers
property and the first entry of the array (which is our TableController
). Our TableController
will need to be able to act upon the restoreUserActivityState
call, so add the following to the TableController
class:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
override func restoreUserActivityState(activity: NSUserActivity) { self.navigationController?.popToRootViewControllerAnimated(true) let iata = activity.userInfo!["iata"] as! String for var index = 0; index < Airports.count; index++ { if Airports[index]["iata"] == iata { let indexPath = NSIndexPath(forRow: index, inSection: 0) self.tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: .None) self.performSegueWithIdentifier("SelectAirportSegue", sender: nil) break; } } super.restoreUserActivityState(activity) } |
Recall that the NSUserActivity
object is just like an NSNotification
; it has a name and is bundled with a dictionary of attributes. In our Apple Watch routines we called
1 2 3 |
updateUserActivity("it.iachieved.handoffexample.viewing.map", userInfo:["iata":self.airport!["iata"]!], webpageURL:nil) |
and provided a key-value pair for the IATA code of the airport we were viewing. Unpack this value in the restoreUserActivityState
routine of the iOS TableController
to find the appropriate Airports
array index. Once we find the index set the selectedIndex
property and then invoke the segue for the TableController
. That’s it!
That’s it! You should be able to successfully test Handoff with your paired Apple Watch and iPhone!
- Connect your iPhone to your Mac
- Open the handoffexample project in Xcode
- Select the Airports! scheme with your iPhone as the target to run on
- Run the application
The Airports! application will load and run on your iPhone, and at the same time the iPhone will communicate to your Apple Watch the availability of the application. Now,
- lock your iPhone
- go to the Home screen of your Apple Watch
- launch Airports! (white airplane in a blue circle)
- select an airport from the list
In this example, we’ve selected Hartsfield-Jackson International in Atlanta, Georgia:
While the airport map is being displayed on your Apple Watch, press the Home button on the iPhone to bring up the lock screen. You should see a frosted airplane logo in the lower right, like so:
Press the airplane icon and swipe up, and unlock your phone. iOS should go immediately to the Airports! app and display the same map as being displayed on the iPhone!
Final Thoughts
Our Handoff implementation is straightforward. Note that we don’t inspect the contents of the activity in any of our routines because we have only declared a single activity type for our application. If our application provides additional user activities we can inspect the activity type to determine how to walk through our view controllers and update the application. See the Raywenderlich.com Handoff tutorial for an excellent example of this.
Get the Code
Get the code! Below are links for the starter application, a full Handoff implementation, or the Bitbucket repository for the code.
- Starter application – download this to go through the tutorial yourself
- Implementation of Handoff routines – download this if you want a working example of Handoff
- Bitbucket repository
Additional Handoff Tutorials
There are more Handoff tutorials on the web that you may find useful (I know I did):
These tutorials are focused on a more generic implementation of Handoff which can be used for iPhone to Mac handoff, or Mac to iPad, etc. Our tutorial has focused on Handoff from the Apple Watch which is a much more specific use case. For example, Handoff can be used for establishing activity continuation streams between devices.
Follow us on Twitter
Did you enjoy this tutorial? Follow us on Twitter at @iachievedit!