3D Touch Quick Actions Not Working at All

3D Touch Quick actions not working at all

Looks like your app is Scene based. For Scene based apps you can almost forget about the AppDelegate and focus on the SceneDelegate. There are two methods you now need to override in the SceneDelegate and one in the AppDelegate. I'll mimic Apple's guide for clarity:

If the user is opening the app and it's a fresh launch, you handle this in the AppDelegate:

     // AppDelegate.swift
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.

// Grab a reference to the shortcutItem to use in the scene
if let shortcutItem = options.shortcutItem {
shortcutItemToProcess = shortcutItem
}

// Previously this method only contained the line below, where the scene is configured
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

If your app is still running in the background when the user clicks on the shortcut item, you handle that in the SceneDelegate:

    // SceneDelegate.swift
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
// When the user opens the app through a quick action, this is now the method that will be called
(UIApplication.shared.delegate as! AppDelegate).shortcutItemToProcess = shortcutItem
}

Once the scene is ready, you can do what you need to with the shortcut :

    // SceneDelegate.swift
func sceneDidBecomeActive(_ scene: UIScene) {
// Is there a shortcut item that has not yet been processed?
if let shortcutItem = (UIApplication.shared.delegate as! AppDelegate).shortcutItemToProcess {
// In this sample an alert is being shown to indicate that the action has been triggered,
// but in real code the functionality for the quick action would be triggered.
var message = "\(shortcutItem.type) triggered"
if let name = shortcutItem.userInfo?["Name"] {
message += " for \(name)"
}
let alertController = UIAlertController(title: "Quick Action", message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Close", style: .default, handler: nil))
window?.rootViewController?.present(alertController, animated: true, completion: nil)

// Reset the shorcut item so it's never processed twice.
(UIApplication.shared.delegate as! AppDelegate).shortcutItemToProcess = nil
}
}

3D Touch Quick Actions not working properly with SpriteKit

Your code is not working because in your app delegate you create a new instance of GameViewController instead of referencing the current one

let gameViewController = GameViewController() // creates new instance

I am doing exactly what you are trying to do with 3d touch quick actions in 2 of my games. I directly load the scene from the appDelegate, dont try to change the gameViewController scene for this.

I use a reusable helper for this. Assuming you set up everything correctly in your info.plist. (I use small letters in the enum so end your items with .first, .second etc in the info.plist), remove all your app delegate code you had previously for the 3d touch quick actions.
Than create a new .swift file in your project and add this code

This is swift 3 code.

import UIKit

/// Shortcut item delegate
protocol ShortcutItemDelegate: class {
func shortcutItemDidPress(_ identifier: ShortcutItemIdentifier)
}

/// Shortcut item identifier
enum ShortcutItemIdentifier: String {
case first // I use swift 3 small letters so you have to change your spelling in the info.plist
case second
case third
case fourth

private init?(fullType: String) {
guard let last = fullType.componentsSeparatedByString(".").last else { return nil }
self.init(rawValue: last)
}

public var type: String {
return (Bundle.main.bundleIdentifier ?? "NoBundleIDFound") + ".\(rawValue)"
}
}

/// Shortcut item protocol
protocol ShortcutItem { }
extension ShortcutItem {

// MARK: - Properties

/// Delegate
private weak var delegate: ShortcutItemDelegate? {
return self as? ShortcutItemDelegate
}

// MARK: - Methods

func didPressShortcutItem(withOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
guard let shortcutItem = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem else { return false }
didPressShortcutItem(shortcutItem)
return true
}

/// Handle item press
@discardableResult
func didPressShortcutItem(_ shortcutItem: UIApplicationShortcutItem) -> Bool {
guard let _ = ShortcutItemIdentifier(fullType: shortcutItem.type) else { return false }

switch shortcutItem.type {

case ShortcutItemIdentifier.first.type:
delegate?.shortcutItemDidPress(.first)

case ShortcutItemIdentifier.second.type:
delegate?.shortcutItemDidPress(.second)

case ShortcutItemIdentifier.third.type:
delegate?.shortcutItemDidPress(.third)

case ShortcutItemIdentifier.fourth.type:
delegate?.shortcutItemDidPress(.fourth)

default:
return false
}

return true
}
}

Than in your app delegate create an extension with this method (you missed this in your code)

extension AppDelegate: ShortcutItem {

/// Perform action for shortcut item. This gets called when app is active
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
completionHandler(didPressShortcutItem(shortcutItem))
}

Than you need to adjust the didFinishLaunchingWithOptions method in your AppDelegate to look like this

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.

...

return !didPressShortcutItem(withOptions: launchOptions)
}

And than finally create another extension confirming to the ShortcutItem delegate

extension AppDelegate: ShortcutItemDelegate {

func shortcutItemDidPress(_ identifier: ShortcutItemIdentifier) {

switch identifier {
case .first:
let scene = GameScene(size: CGSize(width: 1024, height: 768))
loadScene(scene, view: window?.rootViewController?.view)
case .second:
//
case .third:
//
case .fourth:
//
}
}

func loadScene(scene: SKScene?, view: UIView?, scaleMode: SKSceneScaleMode = .aspectFill) {
guard let scene = scene else { return }
guard let skView = view as? SKView else { return }

skView.ignoresSiblingOrder = true
#if os(iOS)
skView.isMultipleTouchEnabled = true
#endif
scene.scaleMode = scaleMode
skView.presentScene(scene)
}
}

The load scene method I normally have in another helper which is why I pass the view into the func.

Hope this helps.

Static Quick Actions not working in objective c iOS

since iOS(13.0, *) AppDelegate handles its own UIWindow via SceneDelegate's and so the method

@implementation AppDelegate

-(void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler
{
// here shortcut evaluation below ios(13.0)
}

@end

is only invoked on iPhones or iPads with iOS lower Version 13.0

So to make it work in iOS(13.0,*) and above you need to implement it in SceneDelegate with a slightly different name, see below.. (matt was right!)

@implementation SceneDelegate

-(void)windowScene:(UIWindowScene *)windowScene performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
// here shortcut evaluation starting with ios(13.0)
NSLog(@"shortcutItem=%@ type=%@",shortcutItem.localizedTitle, shortcutItem.type);
}

@end

PS: The Apple Example Code for Quick Actions in swift is bogus when you assume you can use it as is in iOS(13.0,*) because it is written for versions earlier iOS < 13.0

EDIT: as asked, here in full beauty for iOS >= 13.0

@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@property (strong, nonatomic) UIWindow * window;
@end

and

-(void)windowScene:(UIWindowScene *)windowScene performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
/* //go to Info.plist open as Source Code, paste in your items with folling scheme
<key>UIApplicationShortcutItems</key>
<array>
<dict>
<key>UIApplicationShortcutItemIconType</key>
<string>UIApplicationShortcutIconTypePlay</string>
<key>UIApplicationShortcutItemTitle</key>
<string>Play</string>
<key>UIApplicationShortcutItemSubtitle</key>
<string>Start playback</string>
<key>UIApplicationShortcutItemType</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).Start</string>
</dict>
<dict>
<key>UIApplicationShortcutItemIconType</key>
<string>UIApplicationShortcutIconTypePlay</string>
<key>UIApplicationShortcutItemTitle</key>
<string>STOP</string>
<key>UIApplicationShortcutItemSubtitle</key>
<string>Stop playback</string>
<key>UIApplicationShortcutItemType</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).Stop</string>
</dict>
</array>
*/

// may look complex but this is 100% unique
// which i would place $(PRODUCT_BUNDLE_IDENTIFIER). in front of types in Info.plist
NSString *devIdent = [NSString stringWithFormat:@"%@.",NSBundle.mainBundle.bundleIdentifier];

if ([shortcutItem.type isEqualToString:[devIdent stringByAppendingString:@"Start"]]) {
completionHandler([self windowScene:windowScene byStoryBoardIdentifier:@"startview"]);

} else if ([shortcutItem.type isEqualToString:[devIdent stringByAppendingString:@"Stop"]]) {
completionHandler([self windowScene:windowScene byStoryBoardIdentifier:@"stopview"]);

} else {
NSLog(@"Arrgh! unrecognized shortcut '%@'", shortcutItem.type);
}
//completionHandler(YES);
}

-(BOOL)windowScene:(UIWindowScene *)windowScene byStoryBoardIdentifier:(NSString *)identifier {
// idear is to return NO if build up fails, so we can startup normal if needed.
UIStoryboard *story = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
if (story!=nil) {
UIViewController *vc = [story instantiateViewControllerWithIdentifier:identifier];
if (vc!=nil) {
if (_window==nil) _window = [[UIWindow alloc] initWithWindowScene:windowScene];
_window.rootViewController = vc;

// .. OR following code..
//[_window.rootViewController presentViewController:vc animated:YES completion:^{ }];
//[_window makeKeyAndVisible];

// should be success here..
return YES;
}
}
// no success here..
return NO;
}

You can do also other things than instantiate a ViewController from Storyboard. In example you could switch a property or something similar.

Trying to get Static 3D touch Quick Actions to work in Obj-C

You also need to check the launchOptions in didFinishLaunchingWithOptions.

So, as the result of the ongoing chat, here's the latest code:

AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[FIRApp configure];

if(launchOptions)
{
UIApplicationShortcutItem *selectedItem = [launchOptions objectForKey:UIApplicationLaunchOptionsShortcutItemKey];

if(selectedItem)
{
[self applyShortcutItem:selectedItem];
}
}
return YES;
}

- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler
{
[self applyShortcutItem:shortcutItem];
}

- (void)applyShortcutItem:(UIApplicationShortcutItem *)shortcutItem
{
ViewController *rootViewController = (ViewController *)[self.window rootViewController];

if([shortcutItem.type isEqualToString:@"DogModeShortcut"])
{
[rootViewController setShortcutAction:LaunchDogMode];
}
else if([shortcutItem.type isEqualToString:@"CatModeShortcut"])
{
[rootViewController setShortcutAction:LaunchCatMode];
}
}

3D Touch Quick Action For Same ViewController

Because iOS is telling your AppDelegate that a Quick Action was selected by the user, you're free to do what you want with it. To get a reference to your ViewController you can just ask the window for the rootViewController (which will be your ViewController). You're free then to do what you need to.

func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
guard let viewController = window?.rootViewController as? ViewController else { return }
viewController.performSomeAction()
}

You could also future proof this a bit and crash when running in debug mode if some point in the future you changed to have a different type of view controller as the root:

func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
guard let viewController = window?.rootViewController as? ViewController else {
assertionFailure("Wrong view controller type!") // This only crashes in debug mode
return
}
viewController.performSomeAction()
}

Swift 3D Touch iOS 10 Home screen quick actions share Item missing

That feature or option is only available for apps that are live on the App Store, it will not show up when testing your app. It is done automatically so there is nothing you will have to do.



Related Topics



Leave a reply



Submit