4

Given that I have rootViewController which is UIApplication.shared.delegate?.window??.rootViewController, I want to grab the active navigation controller, if any.

So far what I've come up with:

guard var controller = rootViewController?.presentedViewController else { return rootViewController as? UINavigationController }
while let presented = controller.presentedViewController {
    controller = presented
}
controller = controller.navigationController ?? controller
return controller as? UINavigationController

Is this sufficient? A co-working gave me this solution but the part I don't understand is rootViewController?.presentedViewController. Shouldn't it be rootViewController?.presentingViewController?

barndog
  • 6,975
  • 8
  • 53
  • 105
  • Suggest to check the answers from [here](https://stackoverflow.com/questions/11637709/get-the-current-displaying-uiviewcontroller-on-the-screen-in-appdelegate-m), grab the top viewController then get it's navigationController – Tj3n Aug 01 '18 at 03:50
  • 1
    you can just use self.navigationController to ge the current active navigationController – vivekDas Aug 01 '18 at 05:00

2 Answers2

16

Use the below extension to grab the top most or current visible UIViewController and UINavigationController.

extension UIApplication {
    
    class func topViewController(_ viewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let nav = viewController as? UINavigationController {
            return topViewController(nav.visibleViewController)
        }
        if let tab = viewController as? UITabBarController {
            if let selected = tab.selectedViewController {
                return topViewController(selected)
            }
        }
        if let presented = viewController?.presentedViewController {
            return topViewController(presented)
        }
        return viewController
    }
    
    class func topNavigationController(_ viewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UINavigationController? {
        
        if let nav = viewController as? UINavigationController {
            return nav
        }
        if let tab = viewController as? UITabBarController {
            if let selected = tab.selectedViewController {
                return selected.navigationController
            }
        }
        return viewController?.navigationController
    }
}

How to use?

let objViewcontroller = UIApplication.topViewController()

OR

let objNavigationController = UIApplication.topNavigation()
Hitesh Surani
  • 12,733
  • 6
  • 54
  • 65
  • No, 1) I don't want an extension and didn't ask for one 2) I don't want a recursive solution and 3) you missed the entire question. If you read my post, I ask two things: 1) is my solution sufficient and 2) why should I be using `presentedViewController` over `presentingViewController`. If you'd like to update your answer to reflect that and it works, then I'll accept. – barndog Aug 01 '18 at 16:54
  • 1
    In SwiftUI I have 4 navigation stacks embedded in a tabview. I was using something like this in UIKit to poptoroot. I was finding it only worked for the first tab. The solution was to find the tabviewcontroller using the above code and then find the selected navigation controller in the tab view. Now I can pop to root from every tab! Thanks! – Francojamesfan Nov 16 '22 at 20:32
0

Is this sufficient?

Only you know that. Does it work reliably in your situation? If yes, it might be sufficient for your needs. But it's not the generally correct answer.

A much more reliable method is, as @vivekDas mentioned in a comment, to use the navigationController method, which returns the nearest view controller in the graph that's a navigation controller.

...the part I don't understand is rootViewController?.presentedViewController. Shouldn't it be rootViewController?.presentingViewController?

No. Let's say you've got two view controllers, a and b, and a presents b. In that case, a.presentedViewController is b, and b.presentingViewController is a. So rootViewController.presentedViewController is the view controller that rootViewController presents. rootViewController.presentingViewController would be the controller that presented rootViewController. But rootViewController is the root of the view controller object graph; by definition it hasn't been presented by any other view controller, so rootViewController.presentingViewController will always be nil.

Caleb
  • 124,013
  • 19
  • 183
  • 272