iOS Programming · · 8 min read

3D Touch Introduction: Building a Digital Scale App and Quick Actions

3D Touch Introduction: Building a Digital Scale App and Quick Actions

With the iPhone 6s and 6s Plus, Apple introduced us an entirely new way to interact with our phones: a hard-press gesture. As you may know, this feature was already available on the Apple Watch and MacBook under the name Force Touch. It — literally — added a new dimension to the user interface.

If you’re wondering why Force Touch was renamed 3D touch on the iPhone, you’re not alone. Shortly after Craig Federighi, who was also clearly baffled on the naming, presented this new capability, the first tweets arose. What was wrong with the Force Touch name? Too many Star Wars jokes?

But there is a difference! Apparently 3D Touch is more sensitive in the way that Force Touch can only detect hard presses, whereas 3D Touch can distinguish multiple levels of touches based on how firmly you press.

Though the change may seem unimportant, it allows developers to make more precise measurements on the iPhone. Take this app called Gravity for example that turns your iPhone into a digital scale with Force Touch. Though it was rejected by Apple for not so clear reasons, the idea is wonderful. So to show you how 3D Touch works, let’s try to make a similar app!

Let’s Get Started

To start with, download this project template I made. Basically, this is just an empty Single View iPhone application. I created the design of the app (UILabels & a UIImage) and connected the IBOutlets in ViewController.swift.

3dtouch-storyboard

The design of our app is rather simple: we have one view controller with 2 labels: one title and one label that will show the percentage of the force on the iPhone.

Let’s get coding! On the iPhone 6s and 6s Plus, the UITouch objects have two new CGFloat properties called force and maximumPossibleForce. Force represents how firm a touch is, where 1.0 stands for an average touch. MaximumPossibleForce indicates the maximum force for a touch.

Whenever a user touches something on the screen, touchesBegan is called, followed by touchesMoved (if the user moves its finger(s) over the screen and then touchedCancelled or TouchesEnded is called depending on the situation. For our purpose, touchesMoved is the only method we need. TouchesMoved has two parameters: touches and event. Touches is a set (an unordered collection of distinct objects) of UITouch objects. There should be exactly one UITouch object in touches, but we can’t be careful enough, so it is strongly advised to check if touches.first (the first UITouch object of the touches set) is nil by using optional binding. Insert the following method in ViewController.swift:

override func touchesMoved(touches: Set, withEvent event: UIEvent?) {
    if let touch = touches.first {
        if #available(iOS 9.0, *) {
            if traitCollection.forceTouchCapability == UIForceTouchCapability.Available {
                // 3D Touch capable
            }
        }
    }
}

In the if clause, we verify if the device is 3D Touch capable. This part is optional if you’re making this project for fun. However, if you are going to put the app onto App Store, it is a must to perform the check since older devices like the iPhone 6 don’t support 3D Touch.

Note that I also checked if the device is running iOS 9.0 or later. I’m doing this with the new #available syntax introduced in Swift 2.0. If you want to learn more about what’s new in Swift 2.0, I recommend you to read this article. Again this verification is optional if your deployment target is 9.0 or up.

To get the force percentage, simply divide the touch’s force by the maximum force (i.e. touch.maximumPossibleForce), which is the maximum possible force for a touch. Then update the the label’s text. You can update the method like this:

override func touchesMoved(touches: Set, withEvent event: UIEvent?) {
    if let touch = touches.first {
        if #available(iOS 9.0, *) {
            if traitCollection.forceTouchCapability == UIForceTouchCapability.Available {
                // 3D Touch capable
                let force = touch.force/touch.maximumPossibleForce
                forceLabel.text = "\(force)% force"
            }
        }
    }
}

If you run the app on iPhone 6s/6s Plus, it should show you the force percentage when you press the screen. However, since we are making a scale, you might want to add the number of grams you are weighing on your iPhone. According to Ryan McLeod, the sensor won’t weigh beyond a maximum weight of ~385g. Thus, the maximumPossibleForce corresponds to 385 grams (about 3.8 Newtons). By simple calculations, you can convert %force to grams. All we have to do is multiply the force percentage by 385. For objects weighing 385 grams or more, we just change the label and say something like “385+ grams”.

Now update the method using the following code snippet:

override func touchesMoved(touches: Set, withEvent event: UIEvent?) {        
    if let touch = touches.first {
        if #available(iOS 9.0, *) {
            if traitCollection.forceTouchCapability == UIForceTouchCapability.Available {
                if touch.force >= touch.maximumPossibleForce {
                    forceLabel.text = "385+ grams"
                } else {
                    let force = touch.force/touch.maximumPossibleForce
                    let grams = force * 385
                    let roundGrams = Int(grams)
                    forceLabel.text = "\(roundGrams) grams"
                }
            }
        }
    }
}

Cool! You’ve made a Digital Scale app.

3d-touch-scale-app

For now, the app doesn’t reset the weight to zero after the object or your touch is removed. You can implement the touchesEnded method to reset the label.

override func touchesEnded(touches: Set, withEvent event: UIEvent?) {
    forceLabel.text = "0 gram"
}

Home Screen Quick Actions

Another great use of 3D Touch is Quick Actions on the home screen. Quick actions give users a shortcut to jump to a part of your app directly. Simply hard press an app icon and you’ll see its shortcuts. With the introduction of 3D Touch, Twitter & Instagram and some other Apple apps were shown using this new feature.

3d-touch-quick-action

Let’s add a Quick Action for our scale app that opens the app with a blue background instead of a white one. To add Quick Actions, open info.plist of your project (Click on the Scale workspace in the project navigator, select the Scale target and go to the Info tab). In the file, add a ‘UIApplicationShortcutItems’ array. Each element of the array is a dictionary containing the properties of one Quick Action:

  • UIApplicationShortcutItemType (required): A String that identifies the Quick Action. Note that this String has to be a unique one, and app specific. A good idea is to prefix it with your bundle ID or some other app unique String.
  • UIApplicationShortcutItemTitle (required): A String that represents the title of the Quick Action and is showed to the user. An example might be “Show last picture taken”.
  • UIApplicationShortcutItemSubtitle (optional): A String under the title of your Quick Action. An example might be “Last picture taken yesterday”. If you want to add an icon to your Quick Action, you have two possibilities: a system icon by Apple or a custom icon by yourself.
  • UIApplicationShortcutItemIconType (optional): A String indicating which system icon you want to be displayed next to your Quick Action.
  • UIApplicationShortcutItemIconFile (optional): A String indicating which custom icon you want to be displayed next to your Quick Action.
  • UIApplicationShortcutItemUserInfo (optional): A Dictionary containing some extra information you want to pass with the Quick Action.

In the array, we define four items to configure the “OpenBlue” Quick Action. Your info.plist should look like this:

3d-touch-infoplist

Note that I used ‘$(PRODUCT_BUNDLE_IDENTIFIER)’ instead of ‘com.appcoda.Scale’ or whatever bundle ID you are using. This is for safety purposes: if, for any reason, I change the Bundle ID in ‘General’, the whole project will be affected and Bundle ID will be changed everywhere. Otherwise, I would have to change it everywhere manually. In your info.plist file, you can see that the Bundle Identifier key is using the same approach: ‘$(PRODUCT_BUNDLE_IDENTIFIER)’ describes a path to the Bundle ID of your project.

The last thing that is left to do, is to implement the Quick action when the user actually launches it. Shortcuts are being handled in AppDelegate.swift by the performActionForShortcutItem method. When the Quick Action is activated, the method will be called. So you have to implement the method to handle the quick actions:

func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
    
    // Handle quick actions
    completionHandler(handleQuickAction(shortcutItem))
    
}

You’re expected to call the completion handler with an appropriate boolean value, depending on the success / failure of the quick action. Here we create a separate function called handleQuickAction to handle the shortcut. A great way to represent multiple Quick Action cases is by using enums with the ‘UIApplicationShortcutItemType’ as raw value. Declare an enum and implement the handleQuickAction method like below to set the background colour to blue, when the app is launched through the quick action:

enum Shortcut: String {
    case openBlue = "OpenBlue"
}

func handleQuickAction(shortcutItem: UIApplicationShortcutItem) -> Bool {
    
    var quickActionHandled = false
    let type = shortcutItem.type.componentsSeparatedByString(".").last!
    if let shortcutType = Shortcut.init(rawValue: type) {
        switch shortcutType {
        case .openBlue:
            self.window?.backgroundColor = UIColor(red: 151.0/255.0, green: 187.0/255.0, blue: 255.0/255.0, alpha: 1.0)
            quickActionHandled = true
        }
    }
    
    return quickActionHandled
}

It’s pretty straightforward. If you run the app now and launch it through quick actions, the background becomes blue.

3d-touch-scale-blue

One Thing to Keep in Mind

But there is one thing you have to keep in mind here. In terms of launch sequence, there is a difference between an app is being launched and an app is begin resumed by a Quick Action. As you know, when an app is launched, the willFinishLaunchingWithOptions and didFinishLaunchingWithOptions methods are called. But when an app is resumed by Quick Actions, it only triggers the performActionForShortcutItem method to execute.

3d-touch-quickaction-methods

If you look into the didFinishLaunchingWithOptions method, we have a line of code to set the background colour to white. This is used when an app is launching normally through the app icon.

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions:
    [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.
    self.window?.backgroundColor = UIColor.whiteColor()
        
    return true
}

That is where the problem is: when you launch the app through a quick action, willFinish, didFinish and then performActionForShortcutItem are called. So the background colour is first set to white and then changed to blue. Clearly, you don’t want to set the background colour to white when a user launches the app through quick actions.

To solve this problem, we have to implement a conditional check in the didFinishLaunchingWithOptions method:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions:
    [NSObject: AnyObject]?) -> Bool {
    print("didFinishLaunchingWithOptions called")
    var isLaunchedFromQuickAction = false
   
    // Check if it's launched from Quick Action
    if let shortcutItem = launchOptions?[UIApplicationLaunchOptionsShortcutItemKey] as? UIApplicationShortcutItem {
        
        isLaunchedFromQuickAction = true
        // Handle the sortcutItem
        handleQuickAction(shortcutItem)
    } else {
        self.window?.backgroundColor = UIColor.whiteColor()
    }
    
    // Return false if the app was launched from a shortcut, so performAction... will not be called.
    return !isLaunchedFromQuickAction
}

To determine if the app is launched by a Quick Action, you can check for the UIApplicationLaunchOptionsShortcutItemKey launch option key. The UIApplicationShortcutItem object is available as the value of the launch option key. If the application is launched by a Quick Action, we simple call handleQuickAction to change the background to blue.

Because we have already handled the quick action in didFinishLaunchingWithOptions, we don’t want to call performActionForShortcutItem to execute handleQuickAction again. So, finally we return a value of false, telling the system not to call the performActionForShortcutItem method.

That’s it! You can now test the app again. The Quick Action should work perfectly.

Conclusion

3D Touch is a great way of adding handy and enjoyable side-features to your app. You have to know, however, that not all devices are supporting 3D Touch yet, though that might change in the future.

After reading this article, you should be able to add Quick Actions to your iOS app and determine the firmness of a touch.

For reference, you can download the complete Xcode project here. As always, leave me comment to share your thought on the tutorial and 3D Touch.

Read next