Reference to currently active window

Hello!

I have spent the larger part of the day trying to determine how to get a reference to the currently active window, in a one-scene app running on iOS 15, so that I can access a reliable reference to the rootViewController.

Before 'scenes' came along we used this:

ctrlr = UIApplication.shared.keywindow.rootViewController

Now, thanks to Apple extending iOS to support multiple instantiations of a running App, that method is deprecated and I can no longer figure out how to get a reference to current-scene/current-window.

Please help me out here. I'm at a complete loss, after hours of research into the docs and forums.

Thank you!

Did you see this one (at the end of the thread) ?

https://stackoverflow.com/questions/26667009/get-top-most-uiviewcontroller

I copy the answer here:

extension UIApplication {
    func topViewController() -> UIViewController? {
        var topViewController: UIViewController? = nil
        if #available(iOS 13, *) {
            for scene in connectedScenes {
                if let windowScene = scene as? UIWindowScene {
                    for window in windowScene.windows {
                        if window.isKeyWindow {
                            topViewController = window.rootViewController
                        }
                    }
                }
            }
        } else {
            topViewController = keyWindow?.rootViewController
        }
        while true {
            if let presented = topViewController?.presentedViewController {
                topViewController = presented
            } else if let navController = topViewController as? UINavigationController {
                topViewController = navController.topViewController
            } else if let tabBarController = topViewController as? UITabBarController {
                topViewController = tabBarController.selectedViewController
            } else {
                // Handle any other third party container in `else if` if required
                break
            }
        }
        return topViewController
    }
}
// It could be used in this way:

let topController = UIApplication.shared.topViewController()
topController?.present(controllerToPresent, animated: true, completion: nil)

If you don't need iOS < 13, you can remove the call to keywindow.

Hope that works for you.

Hey!

Thank you for the quick response!

After all the research I just did this morning, I can see how that would work, but I never would have thought to do it that way myself.

Main question, Apple - Why does getting something so simple have to be so freaking hard now? When is enough enough in expanding the capabilities of iOS? And who the heck is gong to run multiple instantiations of the same app on an iPad???

Just me ranting at the wasted time.

Thank you for you solution. I'll let you know if it works (I'm pretty sure it will!).

Okay. Attempted to use your code as follows (including only changed / added code):

extension UIApplication {
...
        while true {
            if let presented = topViewController?.presentedViewController {
                topViewController = presented
...
            } else if let cptVC = topViewController as? CPTViewController {
                topViewController = cptVC
            } else {
                break
            }
        }
        return topViewController
    }
}

// Used like this:

    let cptVC = UIApplication.shared.topViewController()
...
    (cptVC as! CPTViewController).hidePOI().  // Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

Don't know what I'm doing wrong, but it feels like everything else in Swift - like I'm having to cluge things together until there are no indicated errors, but most of the time don't work anyway. I feel like it's my lack of knowledge somehow, but I don't know what I'm missing.

Main questions:

  1. Why doesn't the declaration of cptVC (in the 'Used like this' section) carry the CPTViewController type from the extension?
  2. I understand why cptVC has to be unwrapped (its an optional result), but I don't understand why it has to be type-cast when used.

Bah!

import UIKit
extension UIApplication {
    var topViewController: UIViewController? {
        var topViewController: UIViewController? = nil
        if #available(iOS 13, *) {
            topViewController = connectedScenes.compactMap {
                return ($0 as? UIWindowScene)?.windows.filter { $0.isKeyWindow  }.first?.rootViewController
            }.first
        } else {
            topViewController = keyWindow?.rootViewController
        }
        if let presented = topViewController?.presentedViewController {
            topViewController = presented
        } else if let navController = topViewController as? UINavigationController {
            topViewController = navController.topViewController
        } else if let tabBarController = topViewController as? UITabBarController {
            topViewController = tabBarController.selectedViewController
        }
        return topViewController
    }
}
// handle any checks for any custom contollers outside of extension
if let controller = UIApplication.shared.topViewController as? CPTViewController { }

There is no need for the loop, waste of cpu cycles.

Thank you again for the quick response!

I’ll give it a try.

Wil

Now, thanks to Apple extending iOS to support multiple instantiations of a running App, that method is deprecated and I can no longer figure out how to get a reference to current-scene/current-window.

Generally, the code in question that needs a window or scene should either be passed in a reference to to those objects, or obtain them through something like this: view.window.windowScene.

Did you see this one (at the end of the thread) ?

Please don't do anything like this "solution". For starters, isKeyWindow will return YES if a particular window is key in its scene as of iOS 15 (and related release on other platforms). isKeyWindow is "scene-level" and not "application-level" now.

Main question, Apple - Why does getting something so simple have to be so freaking hard now? When is enough enough in expanding the capabilities of iOS? And who the heck is gong to run multiple instantiations of the same app on an iPad???

The "solutions" above were never very good. And since iPadOS 13, it was never safe to access the application-level key window because it could be pointing to a completely different scene.

As mentioned above, in most cases, code that needs access to a scene or a window should be given one or can find one via context (e.g a view it owns).

Since multiple system processes can be involved in providing scenes to the app (for example on CarPlay) application-level concepts like UIApplication.shared.keywindow are not only not useful, they are harmful.

that method is deprecated and I can no longer figure out how to get a reference to current-scene/current-window.

Instead of searching for a solution, it would be better to start here. Why does the code in question not have a reference to a scene? Why does it need to reach up to something like UIApplication.shared for this information?

Reference to currently active window
 
 
Q