Search code examples
iosswiftuitabbarcontroller

Navigate programmatically through NavigationController-TabBarController-NavigationController


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.


Solution

  • 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.