'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows?

With the following code I get the warning below it:

let rootController = UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController

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

How do I change the code above to work with UIWindowScene instead?

Replies

That depends on where you are calling this code from. This is just grabbing the first key window that is found in the whole application, which might not belong to the window scene the view hierarchy belongs to that you are dealing with.

If you have a view, the easiest way to access the key window of your scene is to call view.windowScene.keyWindow

  • Sorry for not adding more context... this code is actually in my AppDelegate, not on any direct view.

  • What are you doing with this code in your AppDelegate?

  • That's actually a legitimate question. Seeing the deprecation has made me take a second look at why this code is in the AppDelegate to begin with. I'm going to move it out of there and use the code above that you provided from a view directly. Thanks for the help!

@JimmyCricket Is this for something like document revision conflict? Ideally there'd be a mapping somewhere in your app between iCloud documents and scenes (perhaps with UISceneSession's persistentIdentifier). Then you would know exactly what scene any sort of alert should be presented on.

This is the best type of solution in this case. Tie information together and plumb it where you need it so you can make the right decision. Since iOS 13, we've had to help teams internally do the same, often by adding a UIWindowScene parameter in their API so they have context as to where the interaction took place.

That being said, sometimes doing that level of refactor isn't always possible. The solution above will continue to work. It is just deprecated. Just note that the solution above is guaranteed to be wrong some percentage of the time as the application-level key window is not totally within your control.

  • Oh wow! Thank you so much for this detailed response, that's about the best answer I could have asked for.

    For clarification to the situation: I am asking the user what they'd like to do with the app's local datastore that was imported from CloudKit after an iCloud account change (initiated from a NSUbiquityIdentityDidChange notification; there's a few situations in which I need to explicitly have the users response for how to handle the local data such as merging or replacing). It's a weird sort of alert because the message isn't necessarily tired to any window's content, but the app as whole.

    Theoretically, I could surface this message on all windows and dismiss it when a choice has been made on one of them, but I'd really like to avoid going that extreme :x

  • "Thank you so much for this detailed response, that's about the best answer I could have asked for"

    No problem!

    "For clarification to the situation: I am asking the user what they'd like to do with the app's local datastore that was imported from CloudKit after an iCloud account change (initiated from a NSUbiquityIdentityDidChange notification; It's a weird sort of alert because the message isn't necessarily tired to any window's content, but the app as whole"

    Thanks for the extra explanation here.

    From another comment above: "In this case this is really a user experience question. Look at which scenes are on the screen right now and figure out if it really doesn't matter which scene the alert is presented on. In a lot of cases there might be a scene that is more relevant to what you are doing than others, and that's the scene you probably want to present the alert on."

    This is good advise. In this case, UIApplication.keyWindow would really just be a stand-in for "what scene did the user interact with last". You might already have this information. If you don't, you could do something as simple as saving off a timestamp for when your scenes transitioned to UISceneActivationStateForegroundActive. Though there's likely something that's more applicable to your application that would answer this question. Then, when you have a reasonable scene, display the notification there.

    UIApplication.keyWindow isn't magic and as mentioned it will be wrong some portion of the time as it is not within an application's control. If all you need is "what was recently used" that's definitely something you can build without reliance on deprecated API.

  • What you summed up is exactly along the lines that I was needing to achieve and I think that direction makes perfect sense :D This has been very insightful and likely the best answer I’ve ever received (and maybe even seen) on the forums by framework engineers! Thank you again for that additional follow up and detailed thought example!

Add a Comment

How do i replace the below code: UIApplication.shared.windows.first?.overrideUserInterfaceStyle = .unspecified

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

  • Get the set of scenes from UIApplication.shared There is only one scene in the set on the iPhone, more work is probably required on iPad. Pull that scene out and downcast to UIWindowScene Now you can access windowScene.windows

    let scenes = UIApplication.shared.connectedScenes

    let windowScene = scenes.first as? UIWindowScene

    let window = windowScene?.windows.first

  • this worked for me thank you

Add a Comment

((UIWindowScene *)([UIApplication sharedApplication].connectedScenes.allObjects[0])).statusBarManager.statusBarFrame;

In Objective C I was able to replace:

NSArray  *windows = [[UIApplication sharedApplication] windows];

with

NSArray *scenes=[[[UIApplication sharedApplication] connectedScenes] allObjects];
NSArray *windows=[[scenes objectAtIndex:0] windows];

I follow that with:

   for (UIWindow  *window in windows) {
        if (window.isKeyWindow) {
            foundWindow = window;
            break;
        }
   }
   UIViewController* parentController = foundWindow.rootViewController;
   while( parentController.presentedViewController &&
          parentController != parentController.presentedViewController ){
          parentController = parentController.presentedViewController;
   }
Post not yet marked as solved Up vote reply of PBK Down vote reply of PBK
  • This line "[scenes objectAtIndex:0]" might need to add safety check, since "scenes" might return as nil or empty array. So in this case, "windows" will return as nil.

Add a Comment

  if let window = UIApplication.shared.connectedScenes.map({ $0 as? UIWindowScene }).compactMap({ $0 }).first?.windows.first {             } try this solution it's work for me

let allScenes = UIApplication.shared.connectedScenes
let scene = allScenes.first { $0.activationState == .foregroundActive }
                        
if let windowScene = scene as? UIWindowScene {
         windowScene.keyWindow?.rootViewController?.present(SFSafariViewController(url: url, configuration: conf), animated: isAnimated, completion: nil)
}
static var keyWindow: UIWindow? {
  let allScenes = UIApplication.shared.connectedScenes
  for scene in allScenes {
    guard let windowScene = scene as? UIWindowScene else { continue }
    for window in windowScene.windows where window.isKeyWindow {
       return window
     }
   }
    return nil
}