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)
- 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
}
- make
appSettings
scene delegate member to access it and inContentView
and in shortcut delegate and pass it inContentView
as environment object
let appSettings = AppSettings()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = ContentView().environmentObject(appSettings)
// ...
- 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
}
// ...
}
- 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
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
Swift: Google Maps Draw Waypoint Polyline
Fail to Import Restkit with Cocoapods Dynamic Frameworks
How to Add Icon to a Share Sheet in Swift
How to Make a Custom Mkannotationview with Xib
Swift - Writing a Byte Stream to File
How How to Perform Multiple Alamofire Requests That Are Finished One After Another
Sending Firebase Push Notifications to Logged-In Users Only
Openinmapswithlaunchoptions Not Working
Resizing Uimage When Using Sf Symbols - Uiimage(Systemname:)
Display Table View When Searchbar (From Searchcontroller) Begin Edited Swift
How to Make Xcode Put Starting Brace on New Line in Swift
Why Can't I Use Subscripting on a Ckrecord Object in Swift
Swift Spritekit I Detect a Collison But It Reads the Collision Mulitple Times
Swift Spritekit Arc for Dummies
Swift Callkit Sometimes Can't Activate Loudspeaker After Received Call (Only Incoming Call)