Xcode warning: 'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead

2.9k Views Asked by At

when updating my App's Deployment target to 15.0, i receive this warning:

'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead

I have tried to look on the net what could be done to remediate this, but couldn't find much info on this. Hope you could share some advice.

The line of code i am using where this alert occurred is:

let window = UIApplication.shared.windows[0]

followed by in my ViewDidLoad():

 DispatchQueue.main.async { [weak self] in
        if defaults.bool(forKey: "darkModeBoolSwitch") == true {
            self?.window.overrideUserInterfaceStyle  = .dark
            
        } else if defaults.bool(forKey: "darkModeBoolSwitch") == false {
            self?.window.overrideUserInterfaceStyle  = .light
            
        }
5

There are 5 best solutions below

0
On

I am out-of-date with Apple's recent changes to implement scenes.

I did a little digging, and found a protocol UIWindowSceneDelegate

It looks like you are supposed to add an "Application Scene Manifest" to your app's info.plist file that tells the system the class that serves as the app's window scene delegate.

Then in that class you want to implement the method scene(_:willConnectTo:options:). When that method is called you sould try to cast the UIScene that's passed to you to to a UIWindowScene, and if that cast succeeds, you can ask the window scene for it's window and save it to an instance property.

That should allow you to save a pointer to your app's window and use it when needed.

0
On

An alternative to @DuncanC's solution that may also work for you: UIApplication has a connectedScenes property, which lists all of the currently-active scenes doing work in your application (for most applications, this is just the one main scene).

Of those scenes, you can filter for scenes which are UIWindowScenes (ignoring scenes which are not currently active and in the foreground), and of those, find the first scene which has a window which is key:

extension UIApplication {
    static var firstKeyWindowForConnectedScenes: UIWindow? {
        UIApplication.shared
            // Of all connected scenes...
            .connectedScenes.lazy

            // ... grab all foreground active window scenes ...
            .compactMap { $0.activationState == .foregroundActive ? ($0 as? UIWindowScene) : nil }

            // ... finding the first one which has a key window ...
            .first(where: { $0.keyWindow != nil })?

            // ... and return that window.
            .keyWindow
    }
}

I hesitate to call this extension something like UIApplication.keyWindow because the reason for the deprecation of these APIs is because of the generalization to multi-scene applications, each of which may have its own key window... But this should work.

If you still need to support iOS 14, which does not have UIWindowScene.keyWindow, you can replace the firstWhere(...)?.keyWindow with: flatMap(\.windows).first(where: \.isKeyWindow).

0
On

Also this can help:

public extension UIApplication {
    func currentUIWindow() -> UIWindow? {
        let connectedScenes = UIApplication.shared.connectedScenes
            .filter { $0.activationState == .foregroundActive }
            .compactMap { $0 as? UIWindowScene }
        
        let window = connectedScenes.first?
            .windows
            .first { $0.isKeyWindow }
        return window
    }
}

Usage:

 UIApplication.shared.currentUIWindow()?  //...//
0
On

Every answer that suggests scanning through all of the scenes and picking the first window is simply missing the easy and proper solution. Those solutions also cause issues when running the iOS app on an iPad or Mac when supporting multitasking. You can easily end up referencing the wrong window from the wrong scene.

This question is trying to update the property of a window from a view controller. You can access a view controller's specific window using:

let window = self.view.window

The only trick to this is to ensure it is called while the view controller's view is part of the window hierarchy. In the case of the question's needs, this can be done in the viewIsAppearing method. A view controller's view is not yet in the view hierarchy in viewDidLoad or even (in many cases) viewWillAppear.

override func viewIsAppearing(_ animated: Bool) {
    super.viewIsAppearing(animated)

    // This will always be true inside this method
    if let win = self.view.window {
        win.overrideUserInterfaceStyle = defaults.bool(forKey: "darkModeBoolSwitch") ? .dark : .light
    }
}

Optionally, for this question's case, setting up the window in the scene delegate is probably an even better solution:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let _ = (scene as? UIWindowScene) else { return }

        window?.overrideUserInterfaceStyle = defaults.bool(forKey: "darkModeBoolSwitch") ? .dark : .light
    }
}
4
On

After lot of research, following is what worked for me -

UIWindow *firstWindow = nil;
NSSet *scenes = [[UIApplication sharedApplication] connectedScenes];
NSArray *windows = nil;
for (id aScene in scenes) {
  if ([aScene activationState] == UISceneActivationStateForegroundActive) {
    windows = [aScene windows];
    break;
  }
}

for (UIWindow *window in windows) {
  if (window.isKeyWindow) {
    firstWindow = window;
    break;
  }
}