How to Open to a Specific View Using Home Quick Actions

How to open to a specific view using home quick actions

Here is possible approach by steps (tested with Xcode 11.2 / iOS 13.2)

  1. Add AppSettings class to store mode of views to be presented by shorcut
    class AppSettings: ObservableObject {
enum Mode {
case one
case two
}
@Published var mode: Mode? = nil
}

  1. make appSettings scene delegate member to access it and in ContentView and in shortcut delegate and pass it in ContentView as environment object
    let appSettings = AppSettings()

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

let contentView = ContentView().environmentObject(appSettings)
// ...

  1. activate corresponding mode in shortcut scene delegate
    func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
switch shortcutItem.type {
case "QuickAction1":
appSettings.mode = .one
break
case "QuickAction2":
appSettings.mode = .two
break
default:
break
}
// ...
}

  1. Make ContentView present links conditionally based on selected shortcut mode
    struct ContentView: View {
@EnvironmentObject var appSettings: AppSettings

var body: some View {
NavigationView {
VStack {
NavigationLink(destination: OneView(),
tag: AppSettings.Mode.one,
selection: $appSettings.mode)
{ Text("To One") }
NavigationLink(destination: TwoView(),
tag: AppSettings.Mode.two,
selection: $appSettings.mode)
{ Text("To Two") }
}
}
}
}

How can I make a 3d Touch Quick Action open a certain view controller in my app?

You want to instantiate your target VC from the storyboard, like this:

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let targetVC = storyboard.instantiateViewController(withIdentifier :"productViewController") as! ProductViewController

Then you want to decide what to do with it. Some common options might be a) present it modally, b) push it onto an existing Nav Controller, or c) just make it the rootVC of your app's window. Here's how you would do the latter:

 // option B) push onto an existing nav controller
if let navC = window?.rootViewController as! UINavigationController? {
navC.pushViewController(targetVC, animated: false)
}

// option C) just make it the root VC
window?.rootViewController = targetVC

I think the other methods are well documented elsewhere. What you want depends on the navigation structure of your app (which you didn't include in the question).

Apart from that, do take a look at the sample shortcuts application. And remember that this method, performActionForShortcut, is called when a quick action is used to activate your app from the background. So you will also need to deal with launching your app via a shortcut.

Swift 4 - 3D Touch Quick Actions With Tab Bar & Navigation Controller

Did some more digging and found the solution to it. I used the following code in the AppDelegate:

    let myTabBar = self.window?.rootViewController as? UITabBarController
myTabBar?.selectedIndex = 0
let nvc = myTabBar?.selectedViewController as? UINavigationController
let vc = nvc?.viewControllers.first as? MoreViewController
nvc?.popToRootViewController(animated: false)
return vc!.openPageFor(shortcutIdentifier: shortcutIdentifier)

Then inside my MoreViewController I had a function that will call the different segues based on the shortcutIdentifier.

Launching with UIApplicationShortcutItem

I finally got this working. Here's what my AppDelegate.swift file ended up as;

class AppDelegate: UIResponder, UIApplicationDelegate {

// Properties
var window: UIWindow?
var launchedShortcutItem: UIApplicationShortcutItem?

func applicationDidBecomeActive(application: UIApplication) {

guard let shortcut = launchedShortcutItem else { return }

handleShortcut(shortcut)

launchedShortcutItem = nil

}

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

// Override point for customization after application launch.
var shouldPerformAdditionalDelegateHandling = true

// If a shortcut was launched, display its information and take the appropriate action
if let shortcutItem = launchOptions?[UIApplicationLaunchOptionsShortcutItemKey] as? UIApplicationShortcutItem {

launchedShortcutItem = shortcutItem

// This will block "performActionForShortcutItem:completionHandler" from being called.
shouldPerformAdditionalDelegateHandling = false

}

return shouldPerformAdditionalDelegateHandling
}

func handleShortcut( shortcutItem:UIApplicationShortcutItem ) -> Bool {

// Construct an alert using the details of the shortcut used to open the application.
let alertController = UIAlertController(title: "Shortcut Handled", message: "\"\(shortcutItem.localizedTitle)\"", preferredStyle: .Alert)
let okAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertController.addAction(okAction)

// Display an alert indicating the shortcut selected from the home screen.
window!.rootViewController?.presentViewController(alertController, animated: true, completion: nil)

return handled

}

func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {

completionHandler(handleShortcut(shortcutItem))

}

Much of this was taken from Apple's sample code for UIApplicationShortcuts, and while I'm having my app launch an alert to prove that it is recognizing the proper shortcut was chosen, this could be adapted to your code to pop the view controller.

I think the func applicationDidBecomeActive was the critical part that I was missing, and removing the self.handleShortCut(shortcutItem) from didFinishLaunchingWithOptions (otherwise it was calling handleShortCut twice, it seemed).

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.

Flutter Quick Actions change selected Bottom Navigation Bar item

In the demo below, direct click app will enter First Page and In Quick Action choose Main view will enter Second Page

_handleQuickActions need to use

Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => BottomNavigationBarController(
initialIndex: 1,
)));

and use initial index to control page index

class BottomNavigationBarController extends StatefulWidget {
final int initialIndex;

BottomNavigationBarController({
this.initialIndex,
Key key,
}) : super(key: key);

@override
_BottomNavigationBarControllerState createState() =>
_BottomNavigationBarControllerState();
}

full code

import 'package:flutter/material.dart';
import 'package:quick_actions/quick_actions.dart';
import 'dart:io';

class QuickActionsManager extends StatefulWidget {
final Widget child;
QuickActionsManager({Key key, this.child}) : super(key: key);

_QuickActionsManagerState createState() => _QuickActionsManagerState();
}

class _QuickActionsManagerState extends State<QuickActionsManager> {
final QuickActions quickActions = QuickActions();

@override
void initState() {
super.initState();
_setupQuickActions();
_handleQuickActions();
}

@override
Widget build(BuildContext context) {
return widget.child;
}

void _setupQuickActions() {
quickActions.setShortcutItems(<ShortcutItem>[
ShortcutItem(
type: 'action_main',
localizedTitle: 'Main view',
icon: Platform.isAndroid ? 'quick_box' : 'QuickBox'),
ShortcutItem(
type: 'action_help',
localizedTitle: 'Help',
icon: Platform.isAndroid ? 'quick_heart' : 'QuickHeart')
]);
}

void _handleQuickActions() {
quickActions.initialize((shortcutType) {
if (shortcutType == 'action_main') {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => BottomNavigationBarController(
initialIndex: 1,
)));
} else if (shortcutType == 'action_help') {
print('Show the help dialog!');
}
});
}
}

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'QuickActions Demo',
home: QuickActionsManager(child: BottomNavigationBarController(initialIndex: 0,)));
}
}

class Home extends StatelessWidget {
const Home({Key key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text('Home')));
}
}

class Login extends StatelessWidget {
const Login({Key key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text('Login')));
}
}

class BottomNavigationBarController extends StatefulWidget {
final int initialIndex;

BottomNavigationBarController({
this.initialIndex,
Key key,
}) : super(key: key);

@override
_BottomNavigationBarControllerState createState() =>
_BottomNavigationBarControllerState();
}

class _BottomNavigationBarControllerState
extends State<BottomNavigationBarController> {
final List<Widget> pages = [
FirstPage(
key: PageStorageKey('Page1'),
),
SecondPage(
key: PageStorageKey('Page2'),
),
];

final PageStorageBucket bucket = PageStorageBucket();

int _selectedIndex = 0;

Widget _bottomNavigationBar(int selectedIndex) => BottomNavigationBar(
onTap: (int index) => setState(() => _selectedIndex = index),
currentIndex: selectedIndex,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.add), title: Text('First Page')),
BottomNavigationBarItem(
icon: Icon(Icons.list), title: Text('Second Page')),
],
);

@override
void initState() {
_selectedIndex = widget.initialIndex;
super.initState();
}

@override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: _bottomNavigationBar(_selectedIndex),
body: PageStorage(
child: pages[_selectedIndex],
bucket: bucket,
),
);
}
}

class FirstPage extends StatelessWidget {
const FirstPage({Key key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("First Screen"),
),
body: ListView.builder(itemBuilder: (context, index) {
return ListTile(
title: Text('Lorem Ipsum'),
subtitle: Text('$index'),
);
}),
);
}
}

class SecondPage extends StatelessWidget {
const SecondPage({Key key}) : super(key: key);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: ListView.builder(itemBuilder: (context, index) {
return ListTile(
title: Text('Lorem Ipsum'),
subtitle: Text('$index'),
);
}),
);
}
}

demo, emulator is a little slow when enter Second Page

Sample Image

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];
}
}


Related Topics



Leave a reply



Submit