3
class TopViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        //Code Block 1
        let controller = getTopController()
        print(controller)// Prints out MyTestProject.TopViewController

        //Code Block 2
        let controller2 = getRootController()
        print(controller2)//Prints out nil , because keywindow is also nil upto this point.

        //Code Block 3
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
            let controller2 = self.getRootController()
            print(controller2)// Prints out MyTestProject.TopViewController   
        }
    }

    func getTopController() -> UIViewController? {
        guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
            let sceneDelegate = windowScene.delegate as? SceneDelegate else {
                return nil
        }
        return sceneDelegate.window?.rootViewController
    }

    func getRootController() -> UIViewController? {
        let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
        let topController = keyWindow?.rootViewController
        return topController
    }
}

Since iOS 13 there is two approach to get current active / top view controller of the app. here: getTopController() and getRootController() shows both of the approaches.

As commented in codes besides print() results are different though.

In Code Block 2: getRootController can't find the window yet so it prints out nil. Why is this happening?

Also, which is the full proof method of getting reference to top controller in iOS 13, I am confused now?

Prajeet Shrestha
  • 7,978
  • 3
  • 34
  • 63

2 Answers2

1

The problem is that when your view controller viewDidLoad window.makeKey() has not been called yet.

A possible workaround is to get the first window in the windows array if a key window is not available.

    func getRootController() -> UIViewController? {
        let keyWindow = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) ?? UIApplication.shared.windows.first
        let topController = keyWindow?.rootViewController
        return topController
    }

Please note that this will solve your problem but you should postpone any operation that involve using a key window until it is such.

Mario
  • 470
  • 4
  • 9
  • but why it is not same case for getTopController() method? Window is not nil there even if it's being called in viewDidLoad. – Prajeet Shrestha May 11 '20 at 09:24
  • 1
    The problem is not that the window is nil, the window is there but `isKeyWindow` is false because `window.makeKey()` has not been called yet. – Mario May 11 '20 at 09:30
-1

According to the documentation of UIView, the window property is nil if the view has not yet been added to a window which is the case when viewDidLoad is called.

Try to access that in viewDidAppear

override func viewDidAppear(_ animated: Bool) {
   let controller2 = self.view.window.rootViewController
}
Jawad Ali
  • 13,556
  • 3
  • 32
  • 49