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
You are currently viewing page 2 of 5 of this article. Click here to view the first page.

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!

The Starter Project

Start by downloading the starter project for this Handoff tutorial. Once you’ve downloaded it, open up the project in Xcode and run it in an iPhone simulator.

App Screenshots

It is called ShopSnap. You can build a simple shopping list in this app. A shopping item is represented by a String and you store the shopping list as an Array of strings. Tapping the + button adds a new item to the list and swiping removes an item.

You’ll define two distinct user activities for this app:

  • Viewing the list. If the user is currently viewing the list, you’ll Handoff the entire array of items.
  • Adding or editing an item. If the user is currently adding a new item, you’ll Handoff an “edit” activity for a single item instead.

Setting Your Team

For Handoff to work, both the sending and receiving app must be signed by the same team. Since this app is both the sender and the receiver, this is simple!
Select your ShopSnap project, and in the general tab, switch the Team to your team:

Setting your team

Build and run on one of your Handoff-compatible iOS devices to make sure it runs OK, then continue on.

Configuring Activity Types

The next step is to configure the activity types your app supports. Open Supporting Files\Info.plist and click on the + button that appears next to Information Property List to add a new item under the Information Property List dictionary:

Configuring activity types

Enter NSUserActivityTypes for the key name and make it an Array type, as shown below:

Enter NSUserActivityTypes

Add two items under NSUserActivityTypes (Item 0 and Item 1) and set their types to String. Enter com.razeware.shopsnap.view for Item 0, and com.razeware.shopsnap.edit for Item 1.

Add two items under NSUserActivityTypes

These are arbitrary activity types specific and unique to your app. Since you’ll refer to them from multiple places in the app, it’s good practice to add them as constants in a separate file.

Right-click on the ShopSnap group in the project navigator, select New File \ iOS \ Source \ Swift File. Name the class Constants.swift and ensure your new class is added to the ShopSnap target.

Add the following code to your class:

let ActivityTypeView = "com.razeware.shopsnap.view"
let ActivityTypeEdit = "com.razeware.shopsnap.edit"

let ActivityItemsKey = "shopsnap.items.key"
let ActivityItemKey  = "shopsnap.item.key"

Now you can use these constants for the two different activity types. You’ve also defined some constants for the keys you’ll be using in the user activity’s userInfo dictionary for convenience.

Quick End-to-End Test

Let’s run a quick end-to-end test to make sure that your devices can communicate properly.

Open ListViewController.swift and add the following two functions:

// 1.
func startUserActivity() {
  let activity = NSUserActivity(activityType: ActivityTypeView)
  activity.title = "Viewing Shopping List"
  activity.userInfo = [ActivityItemsKey: ["Ice cream", "Apple", "Nuts"]]
  userActivity = activity
  userActivity?.becomeCurrent()
}

// 2.
override func updateUserActivityState(activity: NSUserActivity) {
  activity.addUserInfoEntriesFromDictionary([ActivityItemsKey: ["Ice cream", "Apple", "Nuts"]])
  super.updateUserActivityState(activity)
}

This is a quick test where you hard code a user activity, to make sure that you can receive it OK on the other end.

Here is what the code above does:

  1. startUserActivity() is a helper function that creates an instance of NSUserActivity with a hardcoded shopping list. Then it starts broadcasting that activity by calling becomeCurrent().
  2. After you call becomeCurrent(), OS will periodically call updateUserActivityState(). UIViewController inherits this method from UIResponder, and you should override this to update the state of your userActivity here.
    Here you update the shopping list with the same hardcoded values as before, since this is just a test. Note that addUserInfoEntriesFromDictionary is the preferred way of mutating userInfo dictionary of NSUserActivity. You should always call super.updateUserActivityState() at the end.

Now you just need to start this method. Add the following line to the beginning of viewDidLoad():

startUserActivity()

That’s the minimum you need to start broadcasting – let’s move on to receiving. Open AppDelegate.swift and add the following code:

func application(application: UIApplication, 
                 continueUserActivity userActivity: NSUserActivity, 
                 restorationHandler: (([AnyObject]!) -> Void)) 
                 -> Bool {
  
  let userInfo = userActivity.userInfo as NSDictionary
  println("Received a payload via handoff: \(userInfo)")
  return true
}

This method on AppDelegate is called when everything goes well and a userActivity is successfully transferred. Here you log a message with the userInfo dictionary of the userActivity. You return true to indicate you handled the user activity.

Let’s try this out! There’s a little bit of coordination required to get this working on two devices, so follow along carefully.

    1. Install and run the app on your first device.
    2. Install and run the app on your second device. Make sure that you are debugging the app in Xcode so you can see your println() output.
  1. Install and run the app on your first device.
  2. Install and run the app on your second device. Make sure that you are debugging the app in Xcode so you can see your println() output.

Note: It is also useful to have the console logs open in this step so you can see any output from iOS, in case there is an issue with the connection. To see the console logs, go to Window\Devices, select your device, and select the icon in the lower left to expand the console area.

Console Log

Note: It is also useful to have the console logs open in this step so you can see any output from iOS, in case there is an issue with the connection. To see the console logs, go to Window\Devices, select your device, and select the icon in the lower left to expand the console area.

Console Log

  1. Put the second device to sleep by pressing the power button. On the same device, press the Home button. If everything works fine, you should see the ShopSnap app icon appear at the left bottom corner of the screen. From there you should be able to launch the app, and see the log message in Xcode’s console:
Received a payload via handoff: {
    "shopsnap.items.key" = (
    "Ice cream",
    Apple,
    Nuts
  );
}

If you don’t see the app icon on the lock screen, close and re-open the app on the originating device. This forces the OS to restart broadcasting again. Also check the device console to see if there are any error messages from Handoff.

Lock screen