Introduction
Handoff is an iOS feature that is used to transfer user activity from one device to another device associated with the same user. For example, if the user is logged into multiple devices and is reading documents on a device, user can continue with another device without having to configure anything. It will ensure smooth continuity of the application with multiple devices.
Handoff feature is available in iOS 8.0 and OSX 10.10. The following diagram explains, in brief, the overall architecture of the handoff feature.
Enable Handoff in iOS Device
To begin Handoff process, the device needs to have Handoff enabled on it. This is done using
Settings -> General ->Handoff -> On
Handoff Interaction
In general, Handoff works on the concept of
- Creating user activity
- Updating user activity
- Continuing user activity
Example
In the below example, we will showcase ways to handle Handoff features on different devices with the same team.
Our app contains the following screens
- Show list of users
- User full details
- Save new user details
First Screen
In this screen, we show list of users
Second Screen
In this screen, we show selected user details
Third Screen
In this screen, we show ways to save user details
NSUserActivity
NSUserActivity is the primary object in Handoff feature that encapsulates the state user activity of app on a particular device. The originating app will create user activity or update on each user activity that can be handed off to different device.
We need to create UserActivity object with activity type, which is used in app in DNS format (example: com.innominds.) and add in info.plist file.
NSUserActivity title should match with any string mentioned in plist with key NSUserActivityType .
Create UserActivity and assign userInfo value, which has to be handed off to other devices and should be set and call the method becomeCurrent () to activate the process.
UserInfo is a dictionary of NSUserActivity to pass data or NSCoding compliant custom objects to the receiving device.
Below is the code to register for user activity with activity types added in info.plist.
func createUserActivity() {
userActivity = NSUserActivity(activityType: HOConstants.AcitityTypeView)
userActivity?.title = "View Contact Activity"
userActivity?.becomeCurrent()
}
func createUserActivity() {
userActivity = NSUserActivity(activityType: HOConstants.AcitityTypeEdit)
userActivity?.title = "Edit Contact Activity"
userActivity?.becomeCurrent()
}
The below mentioned method showcases fetched data from user activity in ‘View Contact’ screen. If the ViewController is launched from Handoff, it will fetch data from UserActivity to show on tableview. Additionally, it will activate UserActivity to listen.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let userInfo = userActivity?.userInfo {
contact = DataBaseManager.shared.getContactDataFromDictionary(dictionary: userInfo as! Dictionary <String, String >)
tblContactInfo.reloadData()
}
createUserActivity()
}
Below mentioned method showcases fetched data from user activity in Edit Contact screen. If ViewController is launched from Handoff, it will fetch data from UserActivity and assign values to corresponding textfields apart from activating UserActivity to listen.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let userInfo = userActivity?.userInfo as? Dictionary <String, String>{
let contact = DataBaseManager.shared.getContactDataFromDictionary(dictionary: userInfo)
txtFirstName.text = contact.firstname
txtLastName.text = contact.lastname
txtPhoneNumber.text = contact.phoneNumber
txtEmail.text = contact.email
}
createUserActivity()
}
Update NSUserActivity
Once the user activity is activated, OS periodically call updateUserActivityState (activity: NSUserActivity) to update user activity, which helps to update it on the device upon which Handoff is being used. To update user activity, we have to implement below mentioned method in UIResponder class
func updateUserActivityState(_ activity: NSUserActivity)
We need to refresh contact information every time updateUserActivityState is called by OS. In View Contact Screen:
override func updateUserActivityState(_ activity: NSUserActivity) {
userActivity?.userInfo = DataBaseManager.shared.getDictionaryFromContactData(contact: contact)
super.updateUserActivityState(activity)
}
In Edit Contact Screen:
override func updateUserActivityState(_ activity: NSUserActivity) {
let contact = Contact ()
contact.firstname = txtFirstName.text
contact.lastname = txtLastName.text
contact.phoneNumber = txtPhoneNumber.text
contact.email = txtEmail.text
let dictionary = DataBaseManager.shared.getDictionaryFromContactData(contact: contact)
activity.addUserInfoEntries (from: dictionary)
super.updateUserActivityState (activity)
}
Handle Continuity
The handling application will have to implement NSUserActivityDelegate method to continue the activity that the user was performing.
NSUserActivityDelegate
NSUserActivityDelegate is a top-level object such as view controller or app delegate, which manages the activity interaction with the app and is responsible to keep user activity and user info up to date whenever user action is transferred to another device.
The user activity that handles Handoff will advertise to nearby devices. We need to implement following delegate methods to handle continuity in other devices.
While receiving app launch method with user activity from Handoff, the app delegate calls the below delegate method. But this method won't get user activity object as it will take some time to download user activity. This delegate method will help to determine the activity type. By returning false, we are passing user information control to OS and if we want to handle continuity of user activity, we have to handle by returning true.
func application (_ application: UIApplication, willContinueUserActivityWithType userActivityType: String) -> Bool {
return false
}
When User Activity is ready with data, below mentioned delegate method will be triggered to navigate the corresponding screen with User Activity userInfo so that the app is able to handle display data and continue from where the user left in the original device.
func application (_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
return true
}
func application (_ application: UIApplication, didFailToContinueUserActivityWithType userActivityType: String, error: Error) {
}
We need to fetch userInfo from UserActivity and show the corresponding screen with data as shown below.
func application (application: UIApplication, continueUserActivity UserActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
if let win = window {
let navController = win.rootViewController as! UINavigationController
let viewController = navController.topViewController as! ViewController
viewController.restoreUserActivityState (UserActivity)
}
return true
}
To maintain continuity, we need to implement UIResponder method restoreUserActivityState(_ activity: NSUserActivity) in each view controller existing in hierarchy up to the view controller of interest and to each one, must call the restoreUserActivityState(_ activity: NSUserActivity) of the next view controller until the topmost is reached. In the last view controller, receive the data and display properly to continue from where the user left.
override func restoreUserActivityState(_ activity: NSUserActivity) {
UserActivity = activity
super.restoreUserActivityState (activity)
}
In Top View controller, we need to handle restoreUserActivityState(_ activity: NSUserActivity) method to navigate to the corresponding screen with user activity.
override func restoreUserActivityState(_ activity: NSUserActivity) {
continuedActivity = activity
if activity.activityType == HOConstants.AcitityTypeEdit {
self.performSegue(withIdentifier: "idSegueEditContact", sender: self)
}
else{
self.performSegue(withIdentifier: "idSegueViewContact", sender: self)
}
super.restoreUserActivityState(activity)
}
To support Activity, the app should follow below guidelines
- Both apps must be released from the same developer with the same developer Team ID
- Receiving app must have user NSUserActivityTypes entry for activity type created by sending app
- Both the apps should be distributed through AppStore and signed with the same developer certificate
Note :
NSUserActivity is available from iOS 8.0 and is part of UIResponder and it has
"UserActivity" property for encapsulating user activity defined to a responder so that
it can be used without declaring property name "UserActivity".
Conclusion
Bringing in a sense of continuity for the suite of products delights the user and increases user engagement. This reduces the frustration of going through the same mundane process to get back to a screen they were at on the other devices. Handoff offers a great user experience when built with multiple devices. If the user wants a bigger screen, they can simply shift to a Mac and if they are on the go, the same app with the same content can be viewed over their iPhone.
This sense of continuity is what makes Handoff’s feature one of the unique and great solutions for iOS. We, at Innominds, have worked on this approach with a couple of clients where the continuity started from phone and ended up with Mac applications.
About Innominds
Innominds is a leading Digital Transformation and Product Engineering company headquartered in San Jose, CA. It offers co-creation services to enterprises for building solutions utilizing digital technologies focused on Devices, Apps, and Analytics. Innominds builds better outcomes securely for its clients through reliable advanced technologies like IoT, Blockchain, Big Data, Artificial Intelligence, DevOps and Enterprise Mobility among others. From idea to commercialization, we strive to build convergent solutions that help our clients grow their business and realize their market vision.
Interested! For any demos or project discussions, please write to us at marketing@innominds.com to know more about our offerings