I have the following issue. When I run my app on my phone, 3d touch the icon and select the quick action it launches the app presenting the correct view controller but when I put the app in background and try to invoke the quick action it just opens the app where I have left it. So to make it work I have to kill my app every time.
Here is my code:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
if shortcutItem.type == "com.traning.Search" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let searchVC = sb.instantiateViewControllerWithIdentifier("searchVC") as! UINavigationController
let root = UIApplication.sharedApplication().keyWindow?.rootViewController
root?.presentViewController(searchVC, animated: false, completion: { () -> Void in
completionHandler(true)
})
}
}
Thanks in advance.
I'm guessing you're trying to present a view controller from a view controller that's not visible. You can use extensions like:
extension UIViewController {
func topMostViewController() -> UIViewController {
if self.presentedViewController == nil {
return self
}
if let navigation = self.presentedViewController as? UINavigationController {
return navigation.visibleViewController.topMostViewController()
}
if let tab = self.presentedViewController as? UITabBarController {
if let selectedTab = tab.selectedViewController {
return selectedTab.topMostViewController()
}
return tab.topMostViewController()
}
return self.presentedViewController!.topMostViewController()
}
}
extension UIApplication {
func topMostViewController() -> UIViewController? {
return self.keyWindow?.rootViewController?.topMostViewController()
}
}
You can place both of these in your app delegate.swift, above your app delegate class, to get the currently visible view controller. Then present the search view controller on that. For example:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
if shortcutItem.type == "com.traning.Search" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let searchVC = sb.instantiateViewControllerWithIdentifier("searchVC") as! UINavigationController
let topViewController = UIApplication.sharedApplication.topMostViewController()
topViewController.presentViewController(searchVC, animated: false, completion: { () -> Void in
completionHandler(true)
})
}
}
Related
in a recent project I implemented the spotlight search and everything works fine (I used a UINavigationController). Now I'm recycling the spotlight search for another project that doesn't contain a UINavigationController but just two UIViewController. Also in this case, everything works fine except the redirection to the detail view controller when a specific item is pressed in the spotlight search. I found out that the problem is in this method and it's because I don't use a UINavigationController. So my question is, how can I change this code to work with only my two UIViewController?
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Swift.Void) -> Bool {
let navController = self.window!.rootViewController as! UINavigationController
if let actID = userActivity.userInfo!["kCSSearchableItemActivityIdentifier"] as? String {
let ricette = DataManager.shared.arra.filter { $0.id.uuidString == actID }
if let ricetta = ricette.first {
debugPrint("Abbiamo la ricetta grazie all'ID")
if let listController = navController.topViewController as? ListController {
if let posizione = DataManager.shared.arra.index(of: ricetta) {
listController.showDetailFromSpotlightSearch(posizione)
}
} else if let ricettaController = navController.visibleViewController as? RicettaController {
ricettaController.ricetta = ricetta
ricettaController.aggiornaInterfaccia()
}
} else { debugPrint("errore ID") }
} else { debugPrint("errore ID") }
return true
}
You should use this two extension to determine the topViewController and the visibleViewController:
extension UIApplication {
class func getVisibleViewController(_ rootViewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let presentedViewController = rootViewController?.presentedViewController {
return getVisibleViewController(presentedViewController)
}
return rootViewController
}}
extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}}
I am for some reason having some troubles figuring out the Shortcut Items in my app. I have followed all the tutorials and I believe that I have done everything right; however, it is acting really weird. The problem is that when you completely close out that app and do the 3D Touch Shortcut item on the home screen, it takes you to the correct part of the app as it should; however, when the app is open in the background (like you just click the home button) it doesn't take you to the correct part of the app, it just opens it. This is really odd and I am not sure what to do because I have followed all the instructions. Thank you so much!
Code:
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
if shortcutItem.type == "com.myapp.newMessage" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "RecentVC") as! UITabBarController
vc.selectedIndex = 2 //this takes me to the contacts controller
let root = UIApplication.shared.keyWindow?.rootViewController
root?.present(vc, animated: false, completion: {
completionHandler(true)
})
} else if shortcutItem.type == "com.myapp.groups" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "RecentVC") as! UITabBarController
vc.selectedIndex = 1 //this takes me to the groups controller
let root = UIApplication.shared.keyWindow?.rootViewController
root?.present(vc, animated: false, completion: {
completionHandler(true)
})
} else {
//more short cut items I will add later
}
}
NOTE: these are static Shortcut Items, and I configured them in the info.plist file, This function that was presented is the only place in the app besides the info.plist file that have anything about the Shortcut Items.
EDIT: This seems like an issue with the RootViewController, because I have tried multiple different ways of doing this, and it still gives me the same warning message
2017-11-22 22:36:46.473195-0800 QuickChat2.0[3055:1068642] Warning: Attempt to present on whose view is not in the window hierarchy!
I just tried to accomplish my goals this way through this post but it gives me the exact same results as before.
You should set a variable in your performActionForShortcutItem that tells you the shortcut type and move the code you have in there into applicationDidBecomeActive. Your could should look like this
var shortcut: String?
func applicationDidBecomeActive(application: UIApplication) {
if let shortcutItem = shortcut {
shortcut = nil
if shortcutItem == "com.myapp.newMessage" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "RecentVC") as! UITabBarController
vc.selectedIndex = 2 //this takes me to the contacts controller
let root = UIApplication.shared.keyWindow?.rootViewController
root?.present(vc, animated: false, completion: nil)
} else if shortcutItem == "com.myapp.groups" {
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "RecentVC") as! UITabBarController
vc.selectedIndex = 1 //this takes me to the groups controller
let root = UIApplication.shared.keyWindow?.rootViewController
root?.present(vc, animated: false, completion: nil)
} else {
//more short cut items I will add later
}
}
}
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: #escaping (Bool) -> Void) {
shortcut = shortcutItem.type
completionHandler(true)
}
I think a cleaner method is to set your flags in the performActionFor method, then check for them once your VC loads. Using this notification will handle the shortcut every time the app becomes active, including launch.
override func viewDidLoad() {
super.viewDidLoad()
// Sends notification if active app is reentered to handle quick actions
NotificationCenter.default.addObserver(self, selector: #selector(handleQuickActions), name: UIApplication.didBecomeActiveNotification, object: nil)
I already created some (static) QuickActions in Info.plist for my iOS app created in Xcode and written in Swift.
I have problems with making them able to open a ViewController. Of cause, I already googled, but nothing worked for me. If this counts: I'm using ViewController managed by a TabBarController. Most tutorials seem to use NavigationController. But, I think it will be done with the segues, right? What code do I need to handle it?
Can anybody provide it please? Or does anybody know a simple manual/tutorial?
Regards, David.
P.S.:
I tried this code, but it seems to work only with NavigationController?!
Code:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void)
{
self.handleShortcutItem(shortcutItem)
completionHandler(true)
}
func handleShortcutItem(shortcutItem: UIApplicationShortcutItem)
{
switch shortcutItem.type {
case "icons.quickaction.home":
self.presentComposeViewController()
default: break
}
}
func presentComposeViewController()
{
guard let navigationController = window?.rootViewController as? UINavigationController else { return }
let identifier = "MyViewController"
let composeViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier(identifier)
navigationController.pushViewController(composeViewController, animated: false)
}
I finally found the solution with the help from #ILikeTau.
I'm using the following code to open my ViewControllers managed by a TabBarController with QuickAction:
#available(iOS 9.0, *)
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
if(shortcutItem.type == "app.quickaction.search"){
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateInitialViewController()
window?.rootViewController = vc
guard let tabBarController = window?.rootViewController as? UITabBarController else { return };
tabBarController.selectedIndex = 2
}
else if(shortcutItem.type == "app.quickaction.home"){
let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateInitialViewController()
window?.rootViewController = vc
guard let tabBarController = window?.rootViewController as? UITabBarController else { return };
tabBarController.selectedIndex = 0
}
}
This code works from both modes: app is in background mode and app is closed. I think this way is easier and shorter than the common way with multiple functions.
Is a way to open a specific View Controller from a Quick Action. I have the following code below in the App Delegate to open a specific View Controller. My app has the same design as the snapchat app i.e. with different View Controllers embedded in a scroll view and I would like to target a specific View Controller e.g. Sunday View Controller.
The idea is to present either the first, second or third Items as selected from the quick action items.
func handleShortCutItem(shortcutItem: UIApplicationShortcutItem) -> Bool {
var handled = false
guard ShortcutIdentifier(fullType: shortcutItem.type) != nil else {
return false
}
guard let shortcutType = shortcutItem.type as String? else {
return false
}
switch (shortcutType) {
case ShortcutIdentifier.First.type:
//Present the Add View Controller
handled = true
let mondayVC : MondayViewController = MondayViewController(nibName: "MondayViewController", bundle: nil)
self.window?.rootViewController?.presentViewController(mondayVC, animated: true, completion: nil)
break
case ShortcutIdentifier.Second.type:
//Present the View Controller related to this Screen
handled = true
let fridayVC : FridayViewController = FridayViewController(nibName: "FridayViewController", bundle: nil)
self.window?.rootViewController?.presentViewController(fridayVC, animated: true, completion: nil)
break
case ShortcutIdentifier.Third.type:
//Present the View Controller related to this Screen
handled = true
let sundayVC : SundayViewController = SundayViewController(nibName: "SundayViewController", bundle: nil)
self.window?.rootViewController?.presentViewController(sundayVC, animated: true, completion: nil)
break
default:
break
}
return handled
}
The add delegate code to run this function is:
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: (Bool) -> Void) {
let handledShortcutItem = self.handleShortCutItem(shortcutItem)
completionHandler(handledShortcutItem)
}
In Storyboard: NavigationController -> UITableViewController
appdelegate.swift
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navVC = storyboard.instantiateViewControllerWithIdentifier("navViewController") as! UINavigationController
if let tableVC = navVC.topViewController as? UITableViewController {
tableVC.searchText = // URL query param
}
self.window?.rootViewController?.presentViewController(navVC, animated: false, completion: nil)
}
return true
}
The problem was when I enter with custom URL in Safari in second time
appUrl://search?q=...
The first time it is Ok, open correctly, but if I come back to Safari to open another URL I got
Warning: Attempt to present <UINavigationController: 0x7fb1fb090000> on <UINavigationController: 0x7fb1fa843400> whose view is not in the window hierarchy!
I think the problem is that the rootViewController tries to present two UIViewController which triggers the warning message.How about presenting the second UIViewController on the presented UIViewController.
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navVC = storyboard.instantiateViewControllerWithIdentifier("navViewController") as! UINavigationController
if let tableVC = navVC.topViewController as? UITableViewController {
tableVC.searchText = // URL query param
}
self.window?.rootViewController?.presentedViewController?.presentViewController(navVC, animated: false, completion: nil)
}
return true
}
Try this
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navVC = storyboard.instantiateViewControllerWithIdentifier("navViewController") as! UINavigationController
if let tableVC = navVC.topViewController as? UITableViewController
{
tableVC.searchText
}
if let presented = window?.rootViewController?.presentedViewController?.presentedViewController
{
presented.dismissViewControllerAnimated(false, completion: {
self.window?.rootViewController?.presentedViewController?.presentViewController(navVC, animated: false, completion: nil)
})
}
else
{
self.window?.rootViewController?.presentedViewController?.presentViewController(navVC, animated: false, completion: nil)
}
return true
}