0
votes

My Storyboard based app has following structure:

NavigationController (NavCa) —> CollectionViewController (VCa) —> present modally —> TabBarController (TabBarC)

TabBarC has two tabs, each embedded in own NavigationController:

TabBar[0] NavigationController (NavCTab0) —> TableViewController (VC01) —> ViewController (VC02) —> ViewController (VC03)

TabBar[1] NavigationController (NavCTab1) —> ViewController (VC11) —> ViewController (VC12)

MY PROBLEM: One of my 3D QuickAction must navigate directly to VC01.

I did check through the site. The topic Swift: Programmatically transitioning from 1 view to another via a TabBar and Navigation Controller looks very close to mine. I tried the code (quick action handler in AppDelegate.swift):

let mainSB = UIStoryboard(name: "Main", bundle: nil)
let tbc = mainSB.instantiateViewController(withIdentifier: “TabBarC”) as? TabBarController
tbc?.selectedIndex = 0
let ncTab0  = tbc?.viewControllers![0] as! UINavigationController

let vc01 = mainSB.instantiateViewController(withIdentifier: “VC01”) as! TableViewController01
let vc02 = mainSB.instantiateViewController(withIdentifier: “VC02”) as! ViewController02
let vc03 = mainSB.instantiateViewController(withIdentifier: “VC03”) as! ViewController03
let arrayOfVC = [vc01, vc02, vc03]
ncTab0.setViewControllers(arrayOfVC, animated: false)

ncTab0.popToViewController(vc01, animated: true)
self.window?.rootViewController?.present(tbc, animated: true, completion: nil)

but nothing happening. Nothing comes out in output window. Nothing happens in the app (if the app was terminated, it starts to VCa screen; if the app was not terminated, it stays where it was before quick action).

Any help is highly appreciated.

P.S. I speak only SWIFT.

EDIT

My AppDelegate.swift with Quick Action Handler:

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        var isLaunchedFromQuickAction = false

        // Check if it's launched from Quick Action
        if let shortcutItem = launchOptions?[UIApplicationLaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem {

            isLaunchedFromQuickAction = true
            _ = handleQuickAction(shortcutItem)
        }

        // Return false if the app was launched from a shortcut, so performAction... will not be called.
        return !isLaunchedFromQuickAction
    }

    func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
        completionHandler(handleQuickAction(shortcutItem))
    }

    func handleQuickAction(_ shortcutItem: UIApplicationShortcutItem) -> Bool {

        var quickActionHandled = false
        let type = shortcutItem.type.components(separatedBy: ".").last!
        if type == "QuickAction1" {
            ... // Navigating to VC01, see piece of code above
            quickActionHandled = true
        }
    }
    return quickActionHandled
}

The same quick action handler I use in my other app. That app has the structure NavigationController --> ViewController1 --> ViewController2 --> ViewController3. And there also one of the quick action navigates to the ViewController2. It works like a charm. The only difference with the new app is the app structure. I cannot win the navigation through all these TabBarControllers, NavigationControllers etc...

EDIT 2

As suggested I tried another Quick Action handling approach from Apple Sample Code (https://developer.apple.com/library/content/samplecode/ApplicationShortcuts/Introduction/Intro.html#//apple_ref/doc/uid/TP40016545-Intro-DontLinkElementID_2). And... It works now. Partly.

If I terminate the app the 3D Quick Actions work perfectly.

BUT. If I do not terminate app and only send it in background - the Quick Actions do not work and in Console I get the same old message: 'Warning: Attempt to present <APP_NAME.TabBarController: 0x1060b1c00> on <UINavigationController: 0x106823200> whose view is not in the window hierarchy!'.

Any ideas?

EDIT 3

THE FINAL SOLUTION.

3D Quick Action made exactly as in Apple Sample Code (https://developer.apple.com/library/content/samplecode/ApplicationShortcuts/Introduction/Intro.html#//apple_ref/doc/uid/TP40016545-Intro-DontLinkElementID_2) In AppDelegate.swift under the switch shortCutType for quick action with navigation to 'VC01'

case ShortcutIdentifier.first.type:
    // Handle shortcut 1 (static).
    let mainSB = UIStoryboard(name: "Main", bundle: nil)
    let tbc = mainSB.instantiateViewController(withIdentifier: "TabBarC") as? TabBarController
    self.window?.rootViewController?.dismiss(animated: true, completion: nil)
    self.window?.rootViewController?.present(tbc!, animated: true, completion: nil)
    handled = true
    break

This code tested and works.

1
If you have a normal StoryBoard based app, you do not need to instantiate your own view controllers. - ryantxr
@ryantxr Please, continue. I'll be glad to see your solution. - Евгений М
Have you verified via the debugger that this code is running at all? It might be helpful to see more of your AppDelegate. Have you compared your AppDelegate code to the ApplicationShortcuts sample Swift application? - ozzieozumo
Do you have segues for your navigation controllers? - ryantxr
@ozzieozumo The code is running. I have no problems with my other 3D Quick Actions. I would say it is a ‘standard’ 3D Quick Action implementation. The problem is the navigation... - Евгений М

1 Answers

1
votes

As per my understanding of your code, I think only three lines of code is sufficient

let mainSB = UIStoryboard(name: "Main", bundle: nil)
let tbc = mainSB.instantiateViewController(withIdentifier: “TabBarC”) as? TabBarController
self.window?.rootViewController.present(tbc, animated: true, completion: nil)

Since tab bars view controllers are seems to set in storyboard only. and the first tab of tabbar Controller is selected by default.

EDIT

Other solution for issue of "attempt to present ...." you can set the flag and in the view controllers viewDidLoad method you can try presenting tabBarViewController same way as in code.