The second edition of the Innovation Day at Netquest was on 20th January. I’ve been a little busy releasing a new game made with GameMaker Studio and other stuff. But no more excuses! 😅

This time I wanted to learn about the 3D Touch API and how to implement it in our existing Nicequest app. There are 4 different ways to use the 3D Touch API that provides Apple:

Home screen quick action API.

UIKit peek and pop API.

Web view peek and pop API.

UITouch force properties.

In this first part of the 3D Touch tutorial for Swift 3 we are going to cover the first way: Home screen quick actions API. Please, note that we’ll need iOS 9 or later and a device with 3D Touch capabilities like the iPhone 6S/plus or the iPhone 7/Plus.

The Tutorial Project

If you want to follow step by step this tutorial, check the Template project commit and download it.

The project has a main tab bar controller which contains two view controllers. The first one displays a list of animals where you’ll be able to check its details. The second view controller is related to the UITouch force properties and it’ll be covered on a next tutorial.

What are we going to learn on this first part of the tutorial? Easy! We want to implement some Quick Actions in order to access the different sections of our app in a faster way. Once you finish this tutorial you’ll be able to do the following:

Exciting, right? 😉 Let’s start for the beginning.

About Home Screen Quick Actions

The main purpose of the Home Screen Quick Actions is to offer a quick access to parts of the app for your users. You are able to set a maximum of 4 Quick Actions. In addition, all the apps will display a fifth Quick Action titled Share <name of your app>, as long as they have been installed from the App Store.

In this tutorial we are going to implement the following Quick Actions:

Relax: It opens the view controller of your second tab.

It opens the view controller of your second tab. Random Animal: It opens the detail view controller of a random animal.

It opens the detail view controller of a random animal. About Foxes: It opens the detail view controller of the Fox.

It opens the detail view controller of the Fox. Animals: It opens the view controller of your first tab.

Add the UIApplicationShortcutItems key

The Quick Actions are defined in the Info.plist file and as you shall know, there are two ways to add the UIApplicationShortcutItems key and its array. The first way is to open the file as Source Code.

And then add the following code:

<key>UIApplicationShortcutItems</key> <array> <dict> <key>UIApplicationShortcutItemIconFile</key> <string>quickActionAnimals</string> <key>UIApplicationShortcutItemTitle</key> <string>quickActionTitleAnimals</string> <key>UIApplicationShortcutItemSubtitle</key> <string>quickActionSubtitleAnimals</string> <key>UIApplicationShortcutItemType</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER).OpenAnimals</string> <key>UIApplicationShortcutItemUserInfo</key> <dict> <key>version</key> <string>${APP_VERSION}</string> </dict> </dict> <dict> <key>UIApplicationShortcutItemIconFile</key> <string>quickActionFox</string> <key>UIApplicationShortcutItemTitle</key> <string>quickActionTitleFox</string> <key>UIApplicationShortcutItemType</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER).OpenFox</string> <key>UIApplicationShortcutItemUserInfo</key> <dict> <key>version</key> <string>${APP_VERSION}</string> </dict> </dict> <dict> <key>UIApplicationShortcutItemIconFile</key> <string>quickActionRandomAnimal</string> <key>UIApplicationShortcutItemTitle</key> <string>quickActionTitleRandom</string> <key>UIApplicationShortcutItemType</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER).OpenRandomAnimal</string> <key>UIApplicationShortcutItemUserInfo</key> <dict> <key>version</key> <string>${APP_VERSION}</string> </dict> </dict> <dict> <key>UIApplicationShortcutItemIconFile</key> <string>quickActionRelax</string> <key>UIApplicationShortcutItemTitle</key> <string>quickActionTitleRelax</string> <key>UIApplicationShortcutItemSubtitle</key> <string>quickActionSubtitleRelax</string> <key>UIApplicationShortcutItemType</key> <string>$(PRODUCT_BUNDLE_IDENTIFIER).OpenRelax</string> <key>UIApplicationShortcutItemUserInfo</key> <dict> <key>version</key> <string>${APP_VERSION}</string> </dict> </dict> </array> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 < key > UIApplicationShortcutItems < / key > < array > < dict > < key > UIApplicationShortcutItemIconFile < / key > < string > quickActionAnimals < / string > < key > UIApplicationShortcutItemTitle < / key > < string > quickActionTitleAnimals < / string > < key > UIApplicationShortcutItemSubtitle < / key > < string > quickActionSubtitleAnimals < / string > < key > UIApplicationShortcutItemType < / key > < string > $ ( PRODUCT_BUNDLE_IDENTIFIER ) . OpenAnimals < / string > < key > UIApplicationShortcutItemUserInfo < / key > < dict > < key > version < / key > < string > $ { APP_VERSION } < / string > < / dict > < / dict > < dict > < key > UIApplicationShortcutItemIconFile < / key > < string > quickActionFox < / string > < key > UIApplicationShortcutItemTitle < / key > < string > quickActionTitleFox < / string > < key > UIApplicationShortcutItemType < / key > < string > $ ( PRODUCT_BUNDLE_IDENTIFIER ) . OpenFox < / string > < key > UIApplicationShortcutItemUserInfo < / key > < dict > < key > version < / key > < string > $ { APP_VERSION } < / string > < / dict > < / dict > < dict > < key > UIApplicationShortcutItemIconFile < / key > < string > quickActionRandomAnimal < / string > < key > UIApplicationShortcutItemTitle < / key > < string > quickActionTitleRandom < / string > < key > UIApplicationShortcutItemType < / key > < string > $ ( PRODUCT_BUNDLE_IDENTIFIER ) . OpenRandomAnimal < / string > < key > UIApplicationShortcutItemUserInfo < / key > < dict > < key > version < / key > < string > $ { APP_VERSION } < / string > < / dict > < / dict > < dict > < key > UIApplicationShortcutItemIconFile < / key > < string > quickActionRelax < / string > < key > UIApplicationShortcutItemTitle < / key > < string > quickActionTitleRelax < / string > < key > UIApplicationShortcutItemSubtitle < / key > < string > quickActionSubtitleRelax < / string > < key > UIApplicationShortcutItemType < / key > < string > $ ( PRODUCT_BUNDLE_IDENTIFIER ) . OpenRelax < / string > < key > UIApplicationShortcutItemUserInfo < / key > < dict > < key > version < / key > < string > $ { APP_VERSION } < / string > < / dict > < / dict > < / array >

The other way is to edit directly your Info.plist file and adding the array and the strings manually:

Let’s see in detail every key added in the Info.plist:

UIApplicationShortcutItemType: A required String that identifies your Quick Action. The String must be unique and app specific, so I add as prefix the $(PRODUCT_BUNDLE_IDENTIFIER) variable to the String ID of every Quick Action.

A required String that identifies your Quick Action. The String must be unique and app specific, so I add as prefix the $(PRODUCT_BUNDLE_IDENTIFIER) variable to the String ID of every Quick Action. UIApplicationShortcutItemTitle: A required String that displays the title of the Quick Action once the user presses the icon of the app. If your app is Localized, you can set the String key of your Localized.string file.

A required String that displays the title of the Quick Action once the user presses the icon of the app. If your app is Localized, you can set the String key of your Localized.string file. UIApplicationShortcutItemSubtitle: An optional String that displays the subtitle of the Quick Action. The subtitle can also be localized as the title.

An optional String that displays the subtitle of the Quick Action. The subtitle can also be localized as the title. UIApplicationShortcutItemIconType: An optional String that displays a system icon provided by the library. Check the UIApplicationShortcutIconType enumeration to see all the possible string keys.

An optional String that displays a system icon provided by the library. Check the UIApplicationShortcutIconType enumeration to see all the possible string keys. UIApplicationShortcutItemIconFile: An optional String that displays an icon image to use from the app’s bundle, or the name of an image in an asset catalog.

An optional String that displays an icon image to use from the app’s bundle, or the name of an image in an asset catalog. UIApplicationShortcutItemUserInfo: An optional, app-defined dictionary. One use for this dictionary is to provide app version information.

Handling a Quick Action

There is one last thing we need to do. Quick Actions have been defined and they will be displayed in the Home Screen, but we need to handle them to perform the proper action. We can handle a Quick Action when the user launches it in the AppDelegate.swift file with the following method:

func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { completionHandler(shouldPerformActionFor(shortcutItem: shortcutItem)) } 1 2 3 func application ( _ application : UIApplication , performActionFor shortcutItem : UIApplicationShortcutItem , completionHandler : @ escaping ( Bool ) -> Void ) { completionHandler ( shouldPerformActionFor ( shortcutItem : shortcutItem ) ) }

The application:shortcutItem:completionHandler: method must call the completion handler with a boolean value depending on a success or failure of the Quick Action. We implement a separated method to check if the app should perform or not the Quick Action:

private func shouldPerformActionFor(shortcutItem: UIApplicationShortcutItem) -> Bool { let shortcutType = shortcutItem.type guard let shortcutIdentifier = ShortcutIdentifier(identifier: shortcutType) else { return false } return selectTabBarItemFor(shortcutIdentifier: shortcutIdentifier) } 1 2 3 4 5 6 7 private func shouldPerformActionFor ( shortcutItem : UIApplicationShortcutItem ) -> Bool { let shortcutType = shortcutItem . type guard let shortcutIdentifier = ShortcutIdentifier ( identifier : shortcutType ) else { return false } return selectTabBarItemFor ( shortcutIdentifier : shortcutIdentifier ) }

The shouldPerformActionFor:shortcutItem: method gets a UIApplicationShortcutItem parameter, which contains the Quick Action ID ( type) that the user has launched. We use the ShortcutIdentifier enumeration and its init? method to check if the Quick Action received is listed or not:

enum ShortcutIdentifier: String { case OpenAnimals case OpenFox case OpenRandomAnimal case OpenRelax init?(identifier: String) { guard let shortIdentifier = identifier.components(separatedBy: ".").last else { return nil } self.init(rawValue: shortIdentifier) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 enum ShortcutIdentifier : String { case OpenAnimals case OpenFox case OpenRandomAnimal case OpenRelax init ? ( identifier : String ) { guard let shortIdentifier = identifier . components ( separatedBy : "." ) . last else { return nil } self . init ( rawValue : shortIdentifier ) } }

In case the guard statement has unwrapped the Quick Action ID, we call the selectTabBarItemFor:shortcutIdentifier: method to open the proper view controller in our tab bar. The code is executed from the AppDelegate class, so we need to get first the main UITabBarController of our app. If the selected Quick Action are the Animals or Relax options, we’ll just select and index of the tab bar. In the other hand if the selected Quick Actions are the Fox or Random options, we’ll select the first index and call the openAnimalFor:shortcutIdentifier: method of the AnimalsViewController .

private func selectTabBarItemFor(shortcutIdentifier: ShortcutIdentifier) -> Bool { guard let myTabBar = self.window?.rootViewController as? UITabBarController else { return false } switch shortcutIdentifier { case .OpenAnimals: myTabBar.selectedIndex = 0 return true case .OpenRelax: myTabBar.selectedIndex = 1 return true case .OpenFox, .OpenRandomAnimal: myTabBar.selectedIndex = 0 guard let nvc = myTabBar.selectedViewController as? UINavigationController else { return false } guard let vc = nvc.viewControllers.first as? AnimalsViewController else { return false } nvc.popToRootViewController(animated: false) return vc.openAnimalFor(shortcutIdentifier: shortcutIdentifier) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private func selectTabBarItemFor ( shortcutIdentifier : ShortcutIdentifier ) -> Bool { guard let myTabBar = self . window ? . rootViewController as ? UITabBarController else { return false } switch shortcutIdentifier { case . OpenAnimals : myTabBar . selectedIndex = 0 return true case . OpenRelax : myTabBar . selectedIndex = 1 return true case . OpenFox , . OpenRandomAnimal : myTabBar . selectedIndex = 0 guard let nvc = myTabBar . selectedViewController as ? UINavigationController else { return false } guard let vc = nvc . viewControllers . first as ? AnimalsViewController else { return false } nvc . popToRootViewController ( animated : false ) return vc . openAnimalFor ( shortcutIdentifier : shortcutIdentifier ) } }

When I make tutorials, I like use the most complete examples that I can in order to help you with a lot of different cases. We’re trying to open a detail view controller inside a navigation view controller, that at the same time it’s inside a tab bar controller. Not bad! 😉

The final step is done in the AnimalsViewController class. We need to implement the method, which will open the proper detail view controller depending on the animal that should be displayed.

func openAnimalFor(shortcutIdentifier: ShortcutIdentifier) -> Bool { switch shortcutIdentifier { case .OpenFox: selectedIndex = IndexPath(row: 1, section: 0) performSegue(withIdentifier: segueIdentifier, sender: nil) case .OpenRandomAnimal: if animals == nil { setupView() } let randomNumber = Int(arc4random_uniform(UInt32(animals!.count))) selectedIndex = IndexPath(row: randomNumber, section: 0) performSegue(withIdentifier: segueIdentifier, sender: nil) default: return false } return true } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func openAnimalFor ( shortcutIdentifier : ShortcutIdentifier ) -> Bool { switch shortcutIdentifier { case . OpenFox : selectedIndex = IndexPath ( row : 1 , section : 0 ) performSegue ( withIdentifier : segueIdentifier , sender : nil ) case . OpenRandomAnimal : if animals == nil { setupView ( ) } let randomNumber = Int ( arc4random_uniform ( UInt32 ( animals ! . count ) ) ) selectedIndex = IndexPath ( row : randomNumber , section : 0 ) performSegue ( withIdentifier : segueIdentifier , sender : nil ) default : return false } return true }

There isn’t too much secret in this method. The most important is that we’re using again the shortcutIdentifier to identify which Quick Action needs to be performed.

Bugs, bugs everywhere

As you should noticed, I’m a human and I make mistakes 😅 I realized when the app runs for the first time it didn’t open the Fox and RandomAnimal Quick Actions. That’s the reason why I created an issue ticket explaining this behavior.

The problem was the guard let animals = self.animals line in the AnimalsViewController class. When the app runs for the first time and calls the openAnimalFor:shortcutIdentifier: method, the animals array is not yet initialized and that’s the reason why the guard statement returns with a false value.

Check the commit to see all the changes made to fix this issue.

Source Code

Check this project on GitHub.