Handoff Tutorial: Getting Started

Learn how to use the new Handoff API introduced in iOS 8 to allow users to continue their activities across different devices. By Soheil Azarpour.

Leave a rating/review
Save for later
Share

Update 04/23/2015: Updated for Xcode 6.3 and Swift 1.2.

Note from Ray: This is an abbreviated version of a chapter from iOS 8 by Tutorials released as part of the iOS 8 Feast to give you a sneak peek of what’s inside the book. We hope you enjoy!

Handoff is a new feature in iOS 8 and OS X Yosemite. Handoff lets you continue an activity uninterrupted when you switch from one device to another – without the need to reconfigure either device.

You can add handoff feature to your iOS and OS X apps in iOS 8 and Yosemite. In this tutorial you’ll learn the basic workings of Handoff and how to use it on iOS with a non-document-based app.

Handoff Overview

Before you dive into the code, it’s important to get a high level overview of some important Handoff concepts:

  • Getting Started: Learn if your devices are Handoff-compatible and run a basic test to make sure things are working.
  • User Activities: Learn about the core unit of work with Handoff: user activities.
  • Activity Types: Learn how you can set an “activity type” on a user activity and what that means.

Getting Started

Not only can Handoff transfer your current activity from an iOS to an OS X device, but it can also transfer your current activity between iOS devices.

At the time of writing this tutorial, Handoff does not work on the iOS simulator. Therefore, to follow along with this tutorial, you will need two Handoff-compatible iOS devices.

Device Compatibility: iOS

To check whether your iOS device is Handoff compatible, go to Settings and select General from the list. If you see Handoff & Suggested Apps in the list, your device is Handoff compatible. The following screenshots show General settings menu of an iPhone 5s (Handoff compatible) and an iPad 3rd Gen (not Handoff compatible), respectively.

iPod Touch 5th Gen Settings (Handoff compatible) and iPad 3rd Gen Settings (not Handoff compatible)

Handoff functionality depends on few things:

  1. An iCloud account: You must be logged in to the same iCloud account on each device you wish to use Handoff.
  2. Bluetooth LE 4.0: Handoff broadcasts activities via Bluetooth LE signals, so both the broadcasting and receiving devices must have Bluetooth LE 4.0 support.
  3. iCloud paired: Devices should have been already paired through iCloud. When you sign into your iCloud account on Handoff compatible devices, each device is paired with other Handoff compatible devices. This is where the magic happens.

At this time, make sure that you have two Handoff compatible devices running iOS 8 or later that are logged onto the same iCloud account.

User Activities

Handoff is based on the concept of a user activity, which is a stand-alone collective unit of information that can be handed off without any dependencies on any other information.

The NSUserActivity class represents an instance of a user activity. It encapsulates the state of the application in a way that can be continued on other devices in a related application.

There are three ways to interact with NSUserActivity objects:

You can use the userInfo dictionary of NSUserActivity to pass native data types or NSCoding-compliant custom objects to the receiving device. Native data types include NSArray, NSData, NSDate, NSDictionary, NSNull, NSNumber, NSSet, NSString, NSUUID, and NSURL. Passing NSURL can be a bit tricky; check out the Best Practices section of this tutorial before using NSURL with Handoff.

Notice that you don’t set userInfo to a new dictionary or update it directly. Instead, you should use the convenience method addUserInfoEntriesFromDictionary().

Later in this tutorial, you’ll learn how to force the user activity to refresh on demand, or how to get a similar callback at the app delegate level.

You can then use the data stored in the NSUserActivity object to re-create the user’s activity. This is where you will update your app so that it can continue the associated activity.

  1. Create user activity: The originating app creates an NSUserActivity and calls becomeCurrent() on it to start the broadcasting process. Here’s a quick example:
    let activity = NSUserActivity(activityType: "com.razeware.shopsnap.view")
    activity.title = "Viewing"
    activity.userInfo = ["shopsnap.item.key": ["Apple", "Orange", "Banana"]]
    self.userActivity = activity;
    self.userActivity?.becomeCurrent()
    
  2. Update user activity: Once an instance of NSUserActivity becomes current, the OS periodically invokes updateUserActivityState(activity:) on your topmost view controller to give you a chance to update the user activity. Here’s an example:
    override func updateUserActivityState(activity: NSUserActivity) {
      let activityListItems = // ... get updated list of items
      activity.addUserInfoEntriesFromDictionary(["shopsnap.item.key": activityListItems])
      super.updateUserActivityState(activity)
    }
    
  3. Receive user activity: When your receiving app launches with a user activity from Handoff, the app delegate calls application(:willContinueUserActivityWithType:). Note this method is not passed an instance of NSUserActivity, because it takes a while until Handoff downloads and transfers the NSUserActivity data to your app.Later on, the following delegate callback invokes once the user activity has been downloaded:
    func application(application: UIApplication, 
                     continueUserActivity userActivity: NSUserActivity,
                     restorationHandler: (([AnyObject]!) -> Void)) 
                     -> Bool {
      
      // Do some checks to make sure you can proceed
      if let window = self.window {
        window.rootViewController?.restoreUserActivityState(userActivity)
      }
      return true
    }
    
let activity = NSUserActivity(activityType: "com.razeware.shopsnap.view")
activity.title = "Viewing"
activity.userInfo = ["shopsnap.item.key": ["Apple", "Orange", "Banana"]]
self.userActivity = activity;
self.userActivity?.becomeCurrent()
override func updateUserActivityState(activity: NSUserActivity) {
  let activityListItems = // ... get updated list of items
  activity.addUserInfoEntriesFromDictionary(["shopsnap.item.key": activityListItems])
  super.updateUserActivityState(activity)
}
func application(application: UIApplication, 
                 continueUserActivity userActivity: NSUserActivity,
                 restorationHandler: (([AnyObject]!) -> Void)) 
                 -> Bool {
  
  // Do some checks to make sure you can proceed
  if let window = self.window {
    window.rootViewController?.restoreUserActivityState(userActivity)
  }
  return true
}

Activity Types

When you create a user activity, you must specify an activity type for the activity. An activity type is simply a unique string, usually in reverse DNS notation, like com.razeware.shopsnap.view.

Each app that is capable of receiving a user activity must declare the activity types that it will accept. This is much like declaring the URL schemes your app supports. For non-document based apps, activity types are listed under the NSUserActivityTypes key at the top level of Info.plist as shown below:

Setting NSUserActivityTypes in the Info.plist of a non-document based app

For an app to support a given activity, there are three requirements:

  • Same team. Both apps must originate from the same developer with the same developer Team ID.
  • Same activity type. The receiving app must have a NSUserActivityTypes entry for the activity type created by the sending app.
  • Be signed. Both apps must be distributed through the App Store or be signed with a Developer certificate.

Now that you’ve learned about the basics of user activities and activity types, let’s dive in to an example!