3D Touch in Swift 3.0
With the introduction of iPhone 6S, Apple presented new feature called 3D Touch. It is a new layer on device’s screen that can detect the force of our touch.
3D Touch allows developers to provide users with the bunch of new experiences when using the app.
3D Touch API can handle three different use cases of new gestures:
- Home screen quick actions
- UITouch force properties
- Peek and Pop
In this tutorial, I would like to show you how to implement all three of new gestures in Swift 3.0 (link to Github repository available at the bottom of this page)
1. Home screen quick actions
Let’s start with quick actions. At the beginning, a small explanation what exactly is a quick action.
Quick actions are shown once you do a force press on the app icon.
It allows the user to start some of the main functionalities of app faster than by opening an app and looking for it inside the application.
As you can see on the image above, in our example app we have three different actions available in our app.
To do that we have to modify Info.plist file and add some new fields.
For each action, we need to add at least three different keys and values.
First of those properties is an icon. We can use one of the icons provided by Apple for some common actions like play, pause, capture a photo or search. The second way is to add our own picture to assets and put its name as a value for key UIApplicationShortcutIconFile (I did that for item 2 in the screenshot above).
Next key is UIApplicationShortcutItemTitle which I guess doesn’t really need explanation. The third one is UIApplicationShortcutItemType, and let’s focus a bit on this one. You need it to identify the type of quick action to perform. Based on the type, we can handle all supported quick actions in one method.
Ok, first step is done, by using force press we can open the new menu on our home screen with all actions added in the list.
Once we’ve done with the first step, we can continue with implementation. At the beginning, good idea is to create an enum with all supported actions regarding their types.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | enum TouchActions: String { case special = "special" case favorite = "favorite" case search = "search" var number: Int { switch self { case .special: return 2 case .favorite: return 1 case .search: return 0 } } } |
I’ve also added computed property number which will be helpful later on.
As a next step, we have to implement one function from UIApplicationDelegate (which is confirmed by AppDelegate).
1 2 3 4 5 6 7 8 9 10 11 12 | func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { guard let type = TouchActions(rawValue: shortcutItem.type) else { completionHandler(false) return } let selectedIndex = type.number (window?.rootViewController as? UITabBarController)?.selectedIndex = selectedIndex completionHandler(true) } |
So when the user executes the forced touch, performActionFor method is called, at the beginning, we have to check if supported type is in our enum cases.
If yes, then we can use its number to load proper ViewController. The important thing here is completionHandler which tells our app that everything went ok and we can go forward and show selected VC to the user. That’s it!
2. UITouch force
Ok, so once we know what we can do with quick actions, let us move on to another example of 3D Touch usage which is a UITouch force.
I’ve implemented it inside SearchViewController.
At the beginning, it is good to detect if the user is able to use 3D Touch at all and create proper behaviour in cases when 3D Touch is not available.
In a case of devices without 3D Touch support this feature is replaced by long press gesture if it’s important for us to have some additional use cases for all our user’s regardless devices that they use.
Easiest way to check if we can use 3D Touch is to create a computed property like that:
1 2 3 | var is3DTouchAvailable: Bool { return view.traitCollection.forceTouchCapability == .available } |
Once we know we can use it, we can start implementing our function which will be using force provided by the user.
The best place in the code to detect the value of this force is method touchesMoved. In this example, I’ve added simple UIView object which will be resized (scaled) based on force we are using.
I’ve used normalised force because currently, the values are between 0 and 6.6666 (more or less) but in the future that could be changed, therefore it is safer to normalise force to get the values from 0 to 1. The next would be increasing that value by 1 to avoid having scale equal to 0 and not visible view at all.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesMoved(touches, with: event) if let touch = touches.first { guard is3DTouchAvailable, circleView.frame.contains(touch.location(in: view)) else { return } let maximumForce = touch.maximumPossibleForce let force = touch.force let normalizedForce = (force / maximumForce) + 1.0; let animation = CGAffineTransform(scaleX: normalizedForce, y: normalizedForce) square.transform = animation } } |
And in touchesEnd:
1 2 3 4 5 | override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesEnded(touches, with: event) square.transform = CGAffineTransform.identity } |
Short description of what is happening above. We have to make sure that location on the screen when the user is touching it, is inside our square. Then I calculate normalised force and use it as a scale for circleView. In touchesEnd, I bring the original scale back.
It’s still easy, isn’t it?
3. Peek and pop
It’s time for the last use case for 3D Touch, peek and pop.
Let’s move to SpecialViewController.
To achieve that peek and pop functionality our view controller has to conform to protocol UIViewControllerPreviewingDelegate which contains two methods.
First of them is viewControllerForLocation which is a method called once we do force touch and our finger will be still touching the screen.
1 2 3 4 5 6 7 8 | func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { guard let favoriteViewController = storyboard?.instantiateViewController(withIdentifier: "favoriteVC"), peekAndPopButton.frame.contains(location) else { return nil} favoriteViewController.preferredContentSize = CGSize(width: 0, height: 300.0) return favoriteViewController } |
The result of this function is a FavoriteViewController with changed content size, and it looks like that:
When we press our finger deeper, the second function will be called and FavoriteViewController will be presented on full screen. Implementation of this function looks like that:
1 2 3 | func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { tabBarController?.selectedIndex = TouchActions.favorite.number } |
And that’s all!
As you can see above, implementation of presented examples of 3D Touch functionality is really easy and may be a great improvement to your apps. Questions? Feel free to ask below!
You can find the complete source code on Droids on Roids’s GitHub repository.
Ready to take your business to the next level with a digital product?
We'll be with you every step of the way, from idea to launch and beyond!
Thanks for this. The Info.plist key should be “UIApplicationShortcutItemIconFile” not “UIApplicationShortcutIconFile”