Introduction
With the iPhone 6s and 6s Plus, Apple introduced an entirely new way of interacting with our devices called 3D Touch. 3D Touch works by detecting the amount of pressure that you are applying to your phone's screen in order to perform different actions. In this tutorial, I am going to show you how to take advantage of 3D Touch so that you can utilize this new technology in your own iOS 9 apps.
Prerequisites
This tutorial requires that you are running Xcode 7.1 or later. At the time of writing, the iOS Simulator doesn't support 3D Touch yet, which means that any testing needs to be done on a physical device, iPhone 6s or iPhone 6s Plus. If you'd like to follow along, then start by downloading the starter project from GitHub.
1. Peek and Pop in Storyboards
In this first section, I am going to show you how to implement Peek and Pop functionality in your app using storyboards—and a bit of code. If you don't know what Peek and Pop is, it's basically a way of pressing on a user interface element with a bit more force to get a "Peek" at it.
From such a preview, you can then either lift your finger to dismiss it or push a bit harder again to "Pop" it into full screen. "Peekable" items can be any view controller, including things such as emails, messages, and web pages as shown in the screenshot below.
Open the starter project in Xcode and navigate to Main.storyboard. Zoom the storyboard out by pinching on your trackpad or by pressing Command + -. Select the segue shown in the next screenshot.
With this segue selected, open the Attributes Inspector and look for a new section named Peek and Pop. Enable the checkbox and configure the behavior as shown below.
In this menu, you can assign custom identifiers to both the Peek (Preview) and Pop (Commit) segues. The commit segue also has options to configure everything that you can for a regular segue in storyboards such as Class, Module, and Kind.
Build and run your app on your iPhone and press the + button in the top right corner to create a new item.
Press firmly on the item and you will see that we get a preview of the detail view controller for that item.
You will see that our detail view does not yet show the right data for the item we are previewing. This is because we have not yet configured the view for the custom preview segue we defined in the storyboard. Back in your project, open MasterViewController.swift and replace the prepareForSegue(_:sender:)
method with the following implementation:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "showDetail" { if let indexPath = self.tableView.indexPathForSelectedRow { let object = objects[indexPath.row] as! NSDate let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController controller.detailItem = object controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem() controller.navigationItem.leftItemsSupplementBackButton = true } } else if let cell = sender as? UITableViewCell where segue.identifier == "showDetailPeek" { let controller = (segue.destinationViewController as! UINavigationController).topViewController as! DetailViewController controller.detailItem = cell.textLabel?.text controller.navigationItem.leftBarButtonItem = self.splitViewController?.displayModeButtonItem() controller.navigationItem.leftItemsSupplementBackButton = true } }
The first if
statement remains unchanged. If this code isn't executed, we then check to see if the sender is a UITableViewCell
and the segue identifier is equal to "showDetailPeek"
. If both of these conditions are met, we configure the detailItem
to the text of the cell.
Build and run your app again. This time, when peeking an item, you should get a properly configured preview as shown below.
One important thing to note is that these storyboard configurations for Peek and Pop will only work on devices running iOS 9.1 or later. To support devices running iOS 9.0, you will need to configure your Peek and Pop functionality in code as shown in the next section.
2. Peek and Pop in Code
While a bit more complicated than the storyboard setup, programmatically implementing Peek and Pop also allows you to add extra actions to your previews when the user swipes up. Take a look at the following screenshot to better understand what I mean.
Peek and pop is handled in code by the UIViewControllerPreviewingDelegate
protocol. In your project, create a new iOS > Source > Swift File and name it MasterPreviewing.
Add the following code to MasterPreviewing.swift:
import UIKit extension MasterViewController: UIViewControllerPreviewingDelegate { func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { guard let viewController = self.storyboard?.instantiateViewControllerWithIdentifier("forceViewController") as? ForceViewController else { return nil } viewController.preferredContentSize = CGSize(width: 0, height: 0) return viewController } func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) { showViewController(viewControllerToCommit, sender: self) } }
We make the MasterViewController
class conform to the UIViewControllerPreviewingDelegate
protocol.
In the previewingContext(_:viewControllerForLocation:)
method, we instantiate a ForceViewController
from the storyboard and return this object. The preferredContentSize
property determines how large the Peek preview will appear on the screen. When a size of (0, 0) is used, the preview automatically makes itself as large as it can for the current screen.
In the previewingContext(_:commitViewController:)
method, we complete the transition from the MasterViewController
instance and Pop the ForceViewController
we created at the Peek stage on the screen.
To use this new code, we also need to register specific views which we want to generate a preview for when pushed on firmly. To do this, open MasterViewController.swift and add the following code to viewDidLoad()
:
if traitCollection.forceTouchCapability == .Available { self.registerForPreviewingWithDelegate(self, sourceView: forceButton) }
We first check to see if 3D Touch is available on the device (referred to as Force Touch by the API). If that is the case, we register the forceButton
(the one at the bottom of the table) as an eligible view to Peek and Pop with.
Finally, to add actions to a preview, you need to define them in the preview's view controller class. Open ForceViewController.swift and add the following method to the class:
override func previewActionItems() -> [UIPreviewActionItem] { let regularAction = UIPreviewAction(title: "Regular", style: .Default) { (action: UIPreviewAction, vc: UIViewController) -> Void in } let destructiveAction = UIPreviewAction(title: "Destructive", style: .Destructive) { (action: UIPreviewAction, vc: UIViewController) -> Void in } let actionGroup = UIPreviewActionGroup(title: "Group...", style: .Default, actions: [regularAction, destructiveAction]) return [regularAction, destructiveAction, actionGroup] }
We create three actions to display with the ForceViewController
preview. The first is a regular action and is the most common. When this action is selected the (currently empty) block of code you define when creating the action will be executed. The second is a destructive action that will function exactly the same as the first, but it will appear red on the preview screen. Lastly, we create an action group that collapses any number of other actions under a single button.
Build and run your app once again. This time, push firmly on the Force button to see a new ForceViewController
preview as shown below.
Swipe up on this preview to see the actions that we defined in the ForceViewController
class.
Finally, press the Group... action to open up the actions contained in that group.
3. Detecting Force Through UITouch
On 3D Touch compatible devices, the UITouch
class also gains some new functionality in the form of two new properties, force
and maximumPossibleForce
. These properties are very useful for any use case where you want a precise measurement of how much pressure is being applied to the screen.
Start by adding the following method to the ForceViewController
class:
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) { if let touch = touches.first where traitCollection.forceTouchCapability == .Available { self.forceOutput.text = "\(touch.force)\n\(touch.maximumPossibleForce)" } }
We detect whenever a touch on the screen moves, including towards and away from the screen. We retrieve the current force being applied from the first UITouch
object in the set and display the values on the screen.
Build and run your app and press the Force button to open the ForceViewController
. Push anywhere on the screen with varying amounts of pressure and you will see that the label on the screen updates accordingly to show the current applied force as well as the maximum force that can be applied.
Note that these values are not associated with any physical unit and are independent of the user's 3D Touch sensitivity settings. A value of 1.0 represents the force applied on an average touch.
4. Home Screen Quick Actions
In addition to the new in-app functionality that 3D Touch offers, you can also add up to four shortcuts for specific functions of your application on your app icon. These quick actions can be accessed when a user presses deeply on your app's icon on the home screen as shown in the next screenshot.
There are two main types of quick actions you can create for your app, static and dynamic. Static quick actions are defined in your app's Info.plist and are available at all times for your application. Dynamic quick actions are created in your code and are added to the shared UIApplication
object for your app.
For our app, we are going to create both a static and a dynamic quick action that will have the exact same implementation, adding a new item to the table view. It will show you how to utilize both action types in your own applications.
Quick actions are represented by the new UIApplicationShortcutItem
class, which has the following properties:
localizedTitle
the main title of the quick action (e.g. New Tab in the above screenshot)localizedSubtitle
an optional subtitle for the quick action, which is displayed below the main titletype
a unique string identifier for you to use to determine which quick action was selectedicon
an optionalUIApplicationShortcutIcon
object that can display a system provided icon or a custom imageuserInfo
an optional dictionary, which is useful for associating data with a quick action
Firstly, we are going to create the static quick action. Open the target's Info.plist file and add the following items exactly as shown in the screenshot below:
Note that the UIApplicationShortcutItemIconType key can be swapped with the UIApplicationShortcutItemIconFile key with the value being the image file name you want to use. The UIApplicationShortcutItemUserInfo value we provided is also just a basic example dictionary to show you how you can setup your own custom data.
Next, we are going to create the dynamic action. Open MasterViewController.swift and add the following two lines of code in the viewDidLoad()
method:
let shortcut = UIApplicationShortcutItem(type: "com.tutsplus.Introducing-3D-Touch.add-item", localizedTitle: "Add Item", localizedSubtitle: "Dynamic Action", icon: UIApplicationShortcutIcon(type: .Add), userInfo: nil) UIApplication.sharedApplication().shortcutItems = [shortcut]
Just like that, you have created both a static and dynamic quick action for your application.
Lastly, we need to handle our app's logic for when these quick actions are actually selected from the home screen. This is handled by your app delegate's application(_:performActionForShortcutItem:completionHandler:)
method. Open AppDelegate.swift and add the following method to the AppDelegate
class:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) { if shortcutItem.type == "com.tutsplus.Introducing-3D-Touch.add-item" { let splitViewController = self.window!.rootViewController as! UISplitViewController let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as! UINavigationController let masterViewController = navigationController.viewControllers[0] as! MasterViewController masterViewController.insertNewObject(UIButton()) completionHandler(true) } completionHandler(false) }
We first check the type of the quick action and then access the MasterViewController
object. On this object, we call the insertNewObject(_:)
method to insert a new item into the table view. Note that this method is provided by the the iOS > Application > Master-Detail Application template and requires an AnyObject
parameter. This parameter is not used in the actual method implementation, however, and can be any object. Finally, we call the completionHandler
with a boolean value to tell the system whether or not the quick action was executed successfully.
Build and run your app one last time. Once it has loaded, go to your device's home screen and push firmly on the app icon. You will see that two quick actions are available for your app.
Next, press on either one of these and your application should open with a new item added to the table view.
Conclusion
You should now be comfortable with the 3D Touch APIs available in iOS 9, including Peek and Pop, detecting force via UITouch
, and home screen quick actions. 3D Touch offers many new ways of interacting with your device and I highly encourage everyone to adopt it within their own applications.
As always, you can leave your comments and feedback below.