3d touch quick actions tutorial
With the introduction of the iPhone 6S (plus), Apple added a pressure-sensitive layer to their screen. This creates a bunch of new UX possibilities for creating apps. It’s possible to do a hard press on an application icon and get shortcuts which take you to a specific point in your app. For example, if you do a hard-press on the Photo’s app icon you can quickly search for an image, check the most recent images or see your favourites. It’s also possible to make these quick actions dynamic, meaning that you can add and remove actions based on the state of your application.
In this tutorial I will show you how you can add these quick actions to your application icon.
Define action in Info.plist
The different options which are visible when you press on your application icon are defined in your info.plist file.
First you need to create a new row in your plist and give it the key UIApplicationShortcutItems and set the type to Array. You can now add a dictionary for every quick action.
Select the row you’ve just created and add a new row to the array, set the type to Dictionary.
Within this dictionary you have to add rows with specific keys and values. You can choose from the following options. The ones with a * in front of the name are required.
- * UIApplicationShortcutItemType: an unique string you choose to identify inside of your app which action has to be triggered. For example be.thenerd.my-app.create-user
- * UIApplicationShortcutItemTitle: the string the user sees. If the string is too long, it will be wrapped on 2 lines if there is no subtitle set (see the next key). If a subtitle is set, the string will be truncated.
- UIApplicationShortcutItemSubtitle: optional, string which is shown to the user just below the title.
- UIApplicationShortcutItemIconType: optional, you can use a system-defined icon (the list of keys can be found here)
- UIApplicationShortcutItemIconFile: optional, the name of the image file in your app bundle or assets catalog. The template files to create your custom icon file can be found here.
- UIApplicationShortcutItemUserInfo: optional, a dictionary with custom data you want to use.
<array>
<dict>
<key>UIApplicationShortcutItemIconType</key>
<string>UIApplicationShortcutIconTypeAdd</string>
<key>UIApplicationShortcutItemTitle</key>
<string>Create new user</string>
<key>UIApplicationShortcutItemType</key>
<string>be.thenerd.appshortcut.new-user</string>
<key>UIApplicationShortcutItemUserInfo</key>
<dict>
<key>name</key>
<string>Frederik</string>
</dict>
</dict>
</array>
SwiftThis will give you the following result.
Get notified on selection
Alright, so how do we get notified when the user selects a shortcut? After selection the method application:performActionForShortcutItem:completionHandler gets called. Implement this method in your AppDelegate file.
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
print("Shortcut tapped")
}
SwiftIf you tap on the shortcut you’ll see that this message pops up in your console. But how do we know which shortcut has been tapped?
This method has 3 arguments:
- application: a reference to the shared UIApplication instance
- shortcutItem: a reference to the UIApplicationShortcutItem object
- completionHandler: a closure which gets executed when your quick action code completes.
Remember you have specified the UIApplicationShortcutItemType key? With that unique string you can check which action you have to run. To access this string, you just fetch the value of the type property from the shortcutItem argument.
It might be a good idea to create an enum with all possible values, so you don’t make any spelling mistakes! I’ll create a method handleShortcut:shortcutItem in which you can check what type it is and return a boolean if the action succeeded or not. You need this because the application:performActionForShortcutItem:completionHandler: method requires that you return a boolean via the completionHandler argument.
func handleShortcut( shortcutItem:UIApplicationShortcutItem ) -> Bool {
print("Handling shortcut")
var succeeded = false
if( shortcutItem.type == "be.thenerd.appshortcut.new-user" ) {
// Add your code here
print("- Handling \(shortcutItem.type)")
succeeded = true
}
return succeeded
}
SwiftOne more thing
There is one caveat … there is a difference between launching your application and going from an inactive state to an active state.
This means you are responsible to make sure that application:performActionForShortcutItem:completionHandler: is called conditionally if for example application:didFinishLaunchingWithOptions: has already handled the application shortcut.
So how can we do this?
We will use a boolean in application:didFinishLaunchingWithOptions: to check if we need to call application:performActionForShortcutItem:completionHandler:.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
var performShortcutDelegate = true
...
}
SwiftNow we’ll have to check if the UIApplicationLaunchOptionsShortcutItemKey is available in the launchOptions dictionary argument. If this is the case, we will store the instance of UIApplicationShortcutItem in a property so we can reference it later and set our boolean to false, because we don’t want the application:performActionForShortcutItem:completionHandler: to be called again.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var shortcutItem: UIApplicationShortcutItem?
...
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
print("Application did finish launching with options")
var performShortcutDelegate = true
if let shortcutItem = launchOptions?[UIApplicationLaunchOptionsShortcutItemKey] as? UIApplicationShortcutItem {
print("Application launched via shortcut")
self.shortcutItem = shortcutItem
performShortcutDelegate = false
}
return performShortcutDelegate
}
SwiftNow we can safely implement applicationDidBecomeActive: and check if the shortcutItem property has been set. If so, the app is not coming from a background state.
func applicationDidBecomeActive(application: UIApplication) {
print("Application did become active")
guard let shortcut = shortcutItem else { return }
print("- Shortcut property has been set")
handleShortcut(shortcut)
self.shortcutItem = nil
}
SwiftAnd that’s it!
You can find the source code for this project on Github.
30 Responses
How to i get the controller which action was clicked? I get nil if i try this on viewDidLoad:
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
print(“At Controller (delegate.shortcutItem?.type)”)
I am new to iOS development.
You’ll always end up in the AppDelegate’s method when the user used a quick action. Within that method you’ll have to do a check on the type you have specified for the shortcutItem.
Within that check, you can then create the view controller and present it to the user. I’ve updated the sample project on GitHub, so you can check out the code.
You’ll always end up in the AppDelegate’s method when the user used a quick action. Within that method you’ll have to do a check on the type you have specified for the shortcutItem.
Within that check, you can then create the view controller and present it to the user. I’ve updated the sample project on GitHub, so you can check out the code.
Thanks. Worked great.
app crash with error: fatal error: unexpectedly found nil while unwrapping an Optional value
when i try to edit a UITextField
any way to fix it
I’m adding the outlet as:
@IBOutlet weak var Field: UITextField!
and
func handleShortcut( shortcutItem:UIApplicationShortcutItem ) -> Bool {
print(“Handling shortcut”)
var succeeded = false
if( shortcutItem.type == “item.1” ) {
print(“- Handling (shortcutItem.type)”)
Field.text == “”
succeeded = true
}
Did you check if the Field variable is already initialised. ViewDidLoad hasn’t probably been called yet, so your outlet is still nil which results in a crash if you try to access it at `Field.text`.
Also, you are not doing an assignment on `Field.text`, but an evaluation because you are using double == characters.
You’ll first have to get a reference to your view controller, then assign the value you want to pass to the view controller and visualise it in viewDidLoad (or any lifecycle method which get called after that).
You can check my code on Github where I’ve added a few days ago such an example.
Regards,
Frederik
still not working, the app crash every time i try to change the value of the field, i tried viewdidload, applicationdidfinishlaunching, awakefromnib and also tried making a function that gets activated after 5 seconds from taping the quick action and i used the app for a few seconds before the 5 seconds was over then when the action got called the app crashed.
func handleShortcut( shortcutItem:UIApplicationShortcutItem ) -> Bool {
print(“Handling shortcut”)
var succeeded = false
if( shortcutItem.type == “item.1” ) {
print(“- Handling (shortcutItem.type)”)
self.performSelector(“Action”, withObject: nil, afterDelay: 5)
succeeded = true
}
}
func Action(){
Field.text = “whatever”
}
however when i link a UIButton as an action and use it to change the text in the field the app does not crash
any ideas
I followed your tutorial but quick actions doesn’t appear in springboard. Should I only set quick actions in plist and manage them in AppDelegate, right? Or there’s something else to do? Thank you!
There must be something wrong in the plist if they are not showing (assuming you are testing on an iPhone 6s)
Have a look at the example I’ve done here based on Federik’s tutorial: http://i.imgur.com/ufHxSqH.png
You can leave the icon file, subtitle and type blank to test the shortcut before working on the code to handle them.
Just make sure the structure for UIApplicationShortcutItems looks identical for you.
That’s my info.plist https://uploads.disquscdn.com/images/bd3c584bf2704a20d11ab24613d33746ce38e39619aa5fe2cce0ba5fdac1572e.png
Icon doesn’t contain extension because image is in Asset (and however action should be displayed without icon).
I’ll try writing another type but unluckily I haven’t an iPhone 6S so I have to TestFlight beta to someone which as an iPhone 6S and it’s pretty longer… Would you like to be a tester?!
Ah I see the issue here. The name of the array.
You have:
UIApplicationShortcutItem
change to
UIApplicationShortcutItems
You’ve an eagle eye!! It was the problem!!
Thank you very much!
no worries, i make mistakes like that all the time too 🙂
The way you have handled:
var shortcutItem: UIApplicationShortcutItem?
Works great if the lowest iOS target is 9, but what if we need to support older versions as well? What is the best way to handle this?
I tried the new @available swift2 checking but on AppDelegate – that results in a crash on every device under ios9. Using @available on performActionForShortcutItem works well though.
I’m stuck how to handle this 😐
exactly, the tutorial was not a complete guide !
I’ve got another trouble. I had created my two quick actions. Now I want invert their order on screen so I inverted their order in UIApplicationShortcutItems array but app keep displaying quick actions in previous order. I tried cleaning xCode, removing pp from iPhone, ecc but it’s the same…
as far as I know iOS will use the number in the item dictionary to dictate the order. So Item ‘0’ will always prioritize over Item ‘1’ and so on.
So when you say you inverted the order, did you change the dictionary names to match the order you require?
Item 1 would become Item 0
Item 0 would become Item 1
In inverted title, subtitle, icon and type between item 0 and 1 (copy and paste from an item to another…)
If you want to change the order of your quick actions, you just have to change the order of the items inside the UIApplicationShortcutItems array in your plist like Kevin said.
Same is happening here! How did you end up fixing it?
It’s still in wrong order…
@matte_car:disqus I’ve found a solution!
I reordered the “ tags in my `Info.plist`. I mean, I’ve put it in the wrong order intentionally, then when I run the app, it appears correctly 🙂
Weird, but works!
Example: https://github.com/lucascaton/Segues-and-3D-touch-quick-actions/commit/329750f69ef2a3c40e65fbf424a60e95ba9003e0
You mentioned, “It’s also possible to make these quick actions dynamic, meaning that you can add and remove actions based on the state of your application”, but didn’t follow-up on how that’s done. Any insight into how to make shortcuts dynamic (different options, icons, etc.)? Thanks!
Yes, there is a way. You should check this in apple docs. There is also a sample application for you to play with.
Getting Started with 3D Touch —
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Adopting3DTouchOniPhone/index.html#//apple_ref/doc/uid/TP40016543
In Info.plist before you need: UIApplicationShortcutItems
Yet another amateur question. I’m trying to use 3d Touch for a Spritekite game to start a new game in hard/easy mode. It works when the app wasn’t launched before, but once started, I’m unable to detect if it was started via a shortcut.
The operation with view controllers is still a close book for me, so within the GameScene.swift I’m getting the status with this command:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
forceSelection = appDelegate.forceSelection
At the AppDelegate.swift the selection of the view controller is commented out, because I’ve always got the following error:
*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘Storyboard () doesn’t contain a view controller with identifier ‘Game View Controller”
Anybody who is able to give me a hint?
-Stephan-
Here is a question: in iOS 10, any idea how to hide a Today Widget from the home-screen quick action sheet? I can’t find the method…
Figured it out… in the info.plist, implement this key: “Home Screen Widget” with NULL.
But did that pass to App store ?