Handoff is a great way for your users to resume an activity on any device. Let’s say your users start reading an article on an iPhone, they can easily continue reading it right where they left off on the iPad, or even on the mac. In this article, we’ll see how to use handoff to pass data between the apps in order to provide your users with a seamless experience. It’s quite easy to implement handoff in your apps, setting it up, however, can be a completely different story.
Before The Code
It would be easy to simply jump into the code and show it to you. But, honestly, I’ve spent more time debugging handoff then actually coding it. To save you the pain and hassle of debugging it, I’ll just lay it all out for you here. If your handoff is not working for what ever reason, you’ll need to jump through some hoops first.
WiFi
Make sure your WiFi is enabled and all your devices are on the same WiFi network. This one’s a no-brainer, so let’s move on to the next one.
Bluetooth
This one is a bit weird, I have to say. Obviously, make sure your bluetooth is enabled on all devices. Not all devices support handoff. If your device supports BLE (that’s bluetooth v4.1 and above), you’re good to go.
What I had to do was pair my iPad and my iPhone with my Mac, for some reason. You can do this by going to ‘System Preferences > Bluetooth’ and selecting ‘Pair’ next to your device, just follow the steps to pair the device:
Handoff is using bluetooth to establish a connection between your devices, but, it’s actually using iCloud to send the data.
iCloud
What I had to do was, sign out, and immediately sign back in to my iCloud account on all of my devices that were supposed to use handoff, including my mac. I know, right, turn it off, and turn it back on 🙂
If you do these three steps, you’ll save yourselves a few hours of browsing through stackoverflow trying to figure out those error codes and cursing Apple for making it ‘so easy’.
Now, The Code
I’ll be using my app ‘agostini.tech’ for this example. When you open an article in that app, we’ll create an activity and allow the user to continue reading this article on a different device, including the mac.
Handoff simply passes a small dictionary around, and syncs it across all your connected devices. That’s it. You create a class of type ‘NSUserActivity’, set it’s user data dictionary. If the user selects to continue a session on a different device, the data get’s passed to that device.
You’ll have to go to your .plist file and add a key and a value for your activities:
Take a note of this user activity type, you’ll use it when creating your activities.
In my post details view controller I create an NSUserActivity:
private let myUserActivity: NSUserActivity = NSUserActivity(activityType: "tech.agostini.agostini-tech.postDetails")
I keep it as an instance variable, because I want to invalidate it when the view controller is deallocated:
deinit { myUserActivity.invalidate() }
In ‘viewDidLoad’ I set the userInfo dictionary and webPageURL:
private func setupUserActivity() { myUserActivity.title = selectedPost?.title if let postLinkString = selectedPost?.link { myUserActivity.webpageURL = URL(string: postLinkString) } myUserActivity.userInfo = [ActivityID.postID.rawValue: selectedPost?.postID ?? -1] self.userActivity = myUserActivity self.userActivity?.becomeCurrent() }
We’re creating our dictionary here, that’s pretty straight forward. We’re also setting the webPageURL. If you set this property, you’ll be able to hand off this session to your mac and continue reading the article using a browser, pretty cool 🙂
We’re all set for sending the activity, now we have to receive it and load our view controller.
In you app delegate implement a method that will handle the receiving user activity:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool { if let postsVC = getPostsVC() { restorationHandler([postsVC]) } return true }
I have a function ‘getPostsVC’ that will return a view controller that has a table of posts, when this view controller receives the callback, it will push the post details view controller with the passed in post id. I’m passing the view controller I got from ‘getPosts’ to the ‘restorationHandler’ closure. For every view controller that you pass in here ‘restoreUserActivityState’ function will be called on each one and the user activity will be passed in.
So the last step is to override that function in our view controller:
override func restoreUserActivityState(_ activity: NSUserActivity) { if let postID = activity.userInfo?[ActivityID.postID.rawValue] as? Int, postID > 0, let post = getPosts.getPost(byID: postID), let detailsVC = storyboard?.instantiateViewController(withIdentifier: StoryboardID.postDetails) as? PostDetailsViewController { detailsVC.selectedPost = post self.navigationController?.pushViewController(detailsVC, animated: true) } super.restoreUserActivityState(activity) }
As you can see from the function, we’re getting the post id from the dictionary and querying our controller for that post, if we have it, we push the details view controller. As simple as that 🙂
If you open your details view controller on your device you should see a little icon appear next to your dock on your mac.
On your other devices you’ll see a little app icon on the lock screen.
If you swipe that icon up, you’ll accept the session, and the app will open the details view controller with the data from your other device.
Conclusion
Handoff is real easy to use and if you believe your users will benefit from transferring sessions between devices, there’s no reason not to implement it.
Setting it all up is a different story. You might have to jump through some hoops if your handoff is not working out of the box. There’s not much you can do here (except jump 🙂 ), you can only be consoled by the fact that if handoff is not working for your app it probably isn’t working for all the other apps for that user (misery likes company, right 🙂 ).
This was another one of the ‘short and sweet’ articles. I hope you find it useful and entertaining 🙂 I’m not putting any source code on GitHub for this one. Let’s be honest, it’s only two methods and I don’t want to power up my combine harvester for two corn cobs 🙂
Have a nice day 🙂
Dejan.