How to resolve: 'keyWindow' was deprecated in iOS 13.0
Asked Answered
C

32

360

I'm using Core Data with Cloud Kit, and have therefore to check the iCloud user status during application startup. In case of problems I want to issue a dialog to the user, and I do it using UIApplication.shared.keyWindow?.rootViewController?.present(...) up to now.

In Xcode 11 beta 4, there is now a new deprecation message, telling me:

'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes

screnshot

How shall I present the dialog instead?

Crunode answered 21/7, 2019 at 14:48 Comment(3)
Are you doing this in SceneDelegate or AppDelegate? And, could you post a bit more code so we can duplicate?Bergama
There is no 'keyWindow' concept in iOS anymore as a single app can have multiple windows. You could store the window you create in your SceneDelegate (if you are using SceneDelegate)Houlberg
@Sudara: So, if I have no view controller yet, but want to present an alert - how to do it with a scene? How to get the scene, so that its rootViewController can be retrieved? (So, to make it short: what is the Scene equivalent to the "shared" for UIApplication?)Crunode
G
227

This is my solution:

let keyWindow = UIApplication.shared.connectedScenes
        .filter({$0.activationState == .foregroundActive})
        .compactMap({$0 as? UIWindowScene})
        .first?.windows
        .filter({$0.isKeyWindow}).first

Usage e.g.:

keyWindow?.endEditing(true)
Golden answered 23/7, 2019 at 17:58 Comment(13)
Meanwhile I tested the approach with the multiple scene sample (developer.apple.com/documentation/uikit/app_and_environment/…) and all worked as expected.Golden
You just need the get isKeyWindow.Entopic
It may also be appropriate to test for the activationState value foregroundInactive here, which in my testing will be the case if an alert is presented.Sextet
@Sextet it should be tested because on app start the view controller is already visible but the state is foregroundInactiveColangelo
This code produces keyWindow = nil for me. matt solution is the one that works.Hypnos
This solution is actually not working for me in cases where it's called during applicationWillEnterForeground. The solution @Pipit proposes, works.Companionable
I've updated this answer with a much more performant version of the code, which should give the same result. Let me know if I'm wrong!Utas
.map({$0 as? UIWindowScene}).compactMap({$0}) can be replaced with .compactMap { $0 as? UIWindowScene }Hasen
Nope, this is not the right answer unfortunately—it fails when you have two scenes side by side on the iPad. I have a post here: tengl.net/blog/2021/11/9/uiapplication-key-window-replacementLaundrywoman
true also somtimes I have nil trying to access this wayEnure
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
This didn't work for me, but pommy solution did! Thanks!Waterrepellent
While it works well, there's 1 issue with this solution. It doesn't take into account cases when the app is not active in the foreground. (.filter({$0.activationState == .foregroundActive})) So any UI logic dependent it could result in a visual bug in certain cases (e.g. when deeplinking, the app is technically not active so UI bugs should occur)Mufti
P
432

Edit The suggestion I make here is deprecated in iOS 15. So now what? Well, if an app doesn't have multiple windows of its own, I presume the accepted modern way would be to get the first of the app's connectedScenes, coerce to a UIWindowScene, and take its first window. But that is almost exactly what the accepted answer does! So my workaround feels rather feeble at this point. However, I'll let it stand for historical reasons.


The accepted answer, while ingenious, might be overly elaborate. You can get exactly the same result much more simply:

UIApplication.shared.windows.filter {$0.isKeyWindow}.first

I would also caution that the deprecation of keyWindow should not be taken overly seriously. The full warning message reads:

'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes

So if you are not supporting multiple windows on iPad there is no objection to going ahead and continuing to use keyWindow.

Pipit answered 12/9, 2019 at 2:40 Comment(37)
How would you handle a segue like this let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "homeVC") as! UITabBarController UIApplication.shared.keyWindow?.rootViewController = vc because with iOS 13 and the card view this becomes a problem because a user after say logging out will get pushed to the login screen with the main app in the view hierarchy where they can swipe down and return which is problematic.Bronchia
@LukasBimba That seems unrelated; could you ask it as a separate question? That will make it easier to help. Thanks.Pipit
Here is the question: #58152428Bronchia
Hi @matt. I have a scenario like this. I have a window opened full screen and another window as an overlay in a scene. When i click on a button in the full screen and present a view controller (by the accessing the keyWindow using the above answer), it is presented in the overlay window's view controller. Why does the keyWindow not change to the one i interacted with?Stink
@RakeshaShastri If you are clicking a button in the full screen you don't need the key window. You don't need any window. You just present on self (the view controller that owns the button).Pipit
@Pipit yes. i am aware of that. But i'm doing a custom transition where i have a view controller inside a view. This view is added as a subview to the full screen view. So i end up needing the window's root view controller. Right now i end up calling makeKey() on the view's window before accessing the keyWindow.Stink
@RakeshaShastri Could you ask about this as a separate question? Comments are not the place to iron this out.Pipit
I will as soon as i get time to make a simple, complete, verifiable example.Stink
Hi @matt. In a new project with storyboard UIApplication.shared.windows.filter {$0.isKeyWindow}.first always returns nil if I try to access it from initial view controller. why is that?Kinnikinnick
Why the first window in the array? When I read the documentation for UIWindow's -makeKeyAndVisible and UIApplication's -windows, it seems like we would want the last window. What am I missing?Dual
@Dual It's not the first window in the windows array. It's the first key window in the windows array.Pipit
I thought that the windows array will have windows from all existing scenes, and that each existing scene may have a key window. If that's not the case, what am I misunderstanding? If that is the case, how do we know we want the first and not the last? Thanks!Dual
@Dual But the question presupposes there is only one scene. The problem being solved is merely the deprecation of a certain property. Obviously life is much more complicated if you actually have multiple windows on iPad! If you are really trying to write a multiple window iPad app, good luck to you.Pipit
consider using first(where:) as it is more efficient than your solution (it doesn't create an intermediate array of filtered items)Laaspere
didn't mean to offend you, just noticed that your solution has a lot of upvotes and so many people will use it, though it could be betterLaaspere
@Laaspere Of course it could be better. But it's not wrong. On the contrary, I was the first to suggest that it might be sufficient to ask the application for a window that is the key window, thus avoiding the deprecation of the keyWindow property. Hence the upvotes. If you don't like it, downvote it. But don't tell me to change it to match someone else's answer; that, as I said, would be wrong.Pipit
@Dual said: «I thought [...] that each existing scene may have a key window [...]» The documentation points out: «Only one window at a time may be the key window [for the app, not scene].» As I understand it: Even if you have multiple scenes of your app, the first key window in the windows array is also the only one.Hoon
This now can also be simplified as UIApplication.shared.windows.first(where: \.isKeyWindow)Agouti
@Agouti Yes, I really like that syntax (new in Swift 5.2).Pipit
@LukasBimba you can replace your code to this UIApplication.shared.windows.first?.rootViewController = vc or simply for this one self.view.window?.rootViewController = vcDorena
The accepted answer is actually not working for me in cases where it's called during applicationWillEnterForeground. The solution @Pipit proposes, works.Companionable
@BenLeggiero Edit privileges should neither be used to replace the core content of an answer by the core content of another answer nor to introduce your own writing style.Isopiestic
@Isopiestic The core content remained the same (only performance changed, not behavior nor approach), and my style is different. As the help page says, any time you see a post that needs improvement and are inclined to suggest an edit, you are welcome to do so. [...] Edits are expected to be substantial and to leave the post better than you found it. For us with edit privilege, editing is encouraged!Utas
@BenLeggiero OK but turning my answer into pommy's answer is not encouraged. Do not use editing to lie about history. The history is, I gave an answer, pommy gave a different answer, possibly better. That needs to stand.Pipit
I'm sorry I offended you, @matt; that was not my intention. I also didn't intend do lie about history. I was hoping to help folks coming here from Google trying to find a quick answer who might not be interested in reading through all answers to find newer ones. I consider the edit history to be the history of the answer, not related answers alongside this one.Utas
I honestly thought what I was doing was SO best-practices. I've asked a question on Meta to help me understand what happened here: meta.https://mcmap.net/q/56492/-java-object-reuse/3939277Utas
@BenLeggiero I thought so too until I saw that you were making my answer look like pommy's answer. It's my answer so I need to take a stand, and my stand is, this, for better or worse, is what I said. I've upvoted pommy's answer as being a better expression of mine. Your edit makes me seem to "steal" his answer, and I don't wish to do that. When you edit me, you put your words in my mouth, so that needs to be taken into consideration.Pipit
@Pipit thanks a lot, your simple solution solved my great problem! :)Agreement
Even if you are supporting multiple windows, you should continue to use it, as there's no good workaround. I have a post that discussed the limitations: tengl.net/blog/2021/11/9/uiapplication-key-window-replacement But you are mostly correct. Your answer should be one up top.Laundrywoman
The key window flag will miss when receive remote notification, use the follow code: `` UIApplication.shared.windows.filter {$0.isKeyWindow}.first ?? UIApplication.shared.windows.first ``Elsi
@Elsi If you have a clear use case that you can describe, please add an Answer. Comments can be lost, they are short and can't be formatted well, and people don't notice them. Anyway, I have already said that I prefer https://mcmap.net/q/55605/-how-to-resolve-39-keywindow-39-was-deprecated-in-ios-13-0 so there's little point commenting on my answer.Pipit
a bit shorter: UIApplication.shared.windows.first(where: { $0.isKeyWindow })Dutiable
@OzgurVatansever It hardly matters, because that expression is deprecated.Pipit
How about using this to access first key window UIApplication.shared.firstKeyWindow?Nourish
@SurendraKumar That would be cool if firstKeyWindow existed.Pipit
As an addendum, if you want to find the frontmost, active scene/window, utilize traitCollection: UIApplication.shared.windows.filter { $0.isKeyWindow }.first { $0.traitCollection.activeAppearance == .active }Pelasgian
@Pelasgian I don't think that would be a wise approach.Pipit
I
342

iOS 16-17, compatible down to iOS 15

As this thread keeps getting traffic three years later, I want to share what I consider the most elegant solution with current functionality. It also works with SwiftUI.

UIApplication
    .shared
    .connectedScenes
    .compactMap { ($0 as? UIWindowScene)?.keyWindow }
    .last

iOS 15 and 16, compatible down to iOS 13

UIApplication
    .shared
    .connectedScenes
    .flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
    .last { $0.isKeyWindow }

Note that connectedScenes is available only since iOS 13. If you need to support earlier versions of iOS, you have to place this in an if #available(iOS 13, *) statement.

A variant that is longer, but easier to understand:

UIApplication
    .shared
    .connectedScenes
    .compactMap { $0 as? UIWindowScene }
    .flatMap { $0.windows }
    .last { $0.isKeyWindow }

iOS 13 and 14

The following historical answer is still valid on iOS 15, but should be replaced because UIApplication.shared.windows is deprecated. Thanks to @matt for pointing this out!

Original answer:

Improving slightly on matt's excellent answer, this is even simpler, shorter, and more elegant:

UIApplication.shared.windows.last { $0.isKeyWindow }

Update in April 2023:

Earlier versions of this answer selected the first instead of the last of multiple key windows. As @TengL and @Rob pointed out in comments, this might lead to inconsistent behavior. Even worse, the iOS 13 / 14 solution would select a window that could be hidden behind another. The iOS 16 / 15 solutions might also lead to such an issue, though there is no exact specification.

I have therefore updated all four solution variants in order to increase the chance that the selected key window is actually visible. This should be good enough for most apps running on iOS. More precise control for apps on iPadOS, particularly when they run on macOS, can be obtained by ordering scenes by their activationState or their custom function.

Isopiestic answered 20/9, 2019 at 16:10 Comment(24)
Thank you! Is there a way to do this in objective c?Pulvinate
@Pulvinate Unfortunately NSArray doesn’t have an equivalent to first(where:). You may try to compose a one-liner with filteredArrayUsingPredicate: and firstObject:.Isopiestic
@Pulvinate the code got mangled in the comments section, so I posted an Objective-C equivalent below.Ephod
Xcode 11.2 compiler reported an error with this answer, and suggested adding parenthesis and it's content to first(where:): UIApplication.shared.windows.first(where: { $0.isKeyWindow })Lw
@YassineElBadaoui The parenthesis is not necessary in a simple assignment 89Isopiestic
This now can also be simplified as UIApplication.shared.windows.first(where: \.isKeyWindow)Agouti
@Agouti Your key-path solution is arguably more elegant, but also slightly longer, since you can't drop the the argument label where as with the trailing closure.Isopiestic
@Isopiestic True, I guess it's a matter of personal taste. Xcode doesn't usually indent trailing closures well, that's why I like the other approach better.Agouti
This should have been posted as an edit to Matt's answerUtas
Unfortunately windows is now deprecated too.Pipit
This method fails on one situation: having two scenes in split screen on the iPad, and both are recognized as "Key Window" in this method. But UIApplication.shared.keyWindow returns one (and it's always the one you last interacted with, indicated by the elevated three-dots on top if you connect to a keyboard)Laundrywoman
@TengL you can simply use filter(\.isKeyWindow) instead of first { $0.isKeyWindow } and return an array with all key windowsQuiescent
'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead but shared class doesnt have a property UIWindowScene then whats the answer??Spew
@Spew The answer for iOS 15 derives the windows from those of the connected scenes of the shared UIApplication that are of type UIWindowScene.Isopiestic
Returns nil on iOS 16Halliday
@DeepakSharma The iOS 15 solution still works in my UIKit-based apps on iOS 16.0.2, are you sure? SwiftUI might handle things differently.Isopiestic
@Isopiestic Thanks for keeping this answer updated! Unfortunately I can't upvote it a second time :)Pipit
FWIW, in a multi-window app in iOS 15+, multiple scenes can have a windows with isKeyWindow set. Grabbing the first one leads to inconsistent behavior.Dolly
@Dolly Good point! For the most common cases (single window or window split in two), first just selects any, which is good enough. The now deprecated UIApplication.shared.windows orders the windows from back to front, so the last would ensure visibility. UIApplication.shared.connectedScenes does not specify such an order, but replacing first by last might improve chances of hitting the most visible window. More precise control would distinguish on the scene's activationState or custom title. I will update first to last in all solution variants.Isopiestic
@TengL I am sorry for realizing the full ramifications of your comment only one and a half years later. Please see my answer update and my reply to Rob.Isopiestic
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
Yeah, the question is not first or last, but rather calls for a completely different paradigm for multi-window apps. See Targeting Content with Multiple Windows. Perhaps it is simply beyond the scope of this question, which is predicated on the naive “how do I traverse through the view/window hierarchy” without any context of why they’re doing that, but I just wanted to warn future readers. (I still up-voted this answer. Lol.)Dolly
returns nil on iOS 17 as wellHalliday
@DeepakSharma Yes, the solution will return nil if there are no connected scenes, if none of the connected scenes is a UIWindowScene, or if none of the windows of the connected UIWindowScene's is a key window. The deprecated keyWindow property delivered an optional UIWindow as well, see the documentation. As an update to your last year's comment, I have verified now that the workaround also delivers an actual key window in a SwiftUI app. You might run it in unusual circumstances.Isopiestic
G
227

This is my solution:

let keyWindow = UIApplication.shared.connectedScenes
        .filter({$0.activationState == .foregroundActive})
        .compactMap({$0 as? UIWindowScene})
        .first?.windows
        .filter({$0.isKeyWindow}).first

Usage e.g.:

keyWindow?.endEditing(true)
Golden answered 23/7, 2019 at 17:58 Comment(13)
Meanwhile I tested the approach with the multiple scene sample (developer.apple.com/documentation/uikit/app_and_environment/…) and all worked as expected.Golden
You just need the get isKeyWindow.Entopic
It may also be appropriate to test for the activationState value foregroundInactive here, which in my testing will be the case if an alert is presented.Sextet
@Sextet it should be tested because on app start the view controller is already visible but the state is foregroundInactiveColangelo
This code produces keyWindow = nil for me. matt solution is the one that works.Hypnos
This solution is actually not working for me in cases where it's called during applicationWillEnterForeground. The solution @Pipit proposes, works.Companionable
I've updated this answer with a much more performant version of the code, which should give the same result. Let me know if I'm wrong!Utas
.map({$0 as? UIWindowScene}).compactMap({$0}) can be replaced with .compactMap { $0 as? UIWindowScene }Hasen
Nope, this is not the right answer unfortunately—it fails when you have two scenes side by side on the iPad. I have a post here: tengl.net/blog/2021/11/9/uiapplication-key-window-replacementLaundrywoman
true also somtimes I have nil trying to access this wayEnure
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
This didn't work for me, but pommy solution did! Thanks!Waterrepellent
While it works well, there's 1 issue with this solution. It doesn't take into account cases when the app is not active in the foreground. (.filter({$0.activationState == .foregroundActive})) So any UI logic dependent it could result in a visual bug in certain cases (e.g. when deeplinking, the app is technically not active so UI bugs should occur)Mufti
P
62

Here is a backward-compatible way of detecting keyWindow:

extension UIWindow {
    static var key: UIWindow? {
        if #available(iOS 13, *) {
            return UIApplication.shared.windows.first { $0.isKeyWindow }
        } else {
            return UIApplication.shared.keyWindow
        }
    }
}

Usage:

if let keyWindow = UIWindow.key {
    // Do something
}
Piccard answered 29/1, 2020 at 12:30 Comment(2)
The availability checks are hardly necessary, since windows and isKeyWindow have been around since iOS 2.0, and first(where:) since Xcode 9.0 / Swift 4 / 2017.Isopiestic
UIApplication.keyWindow has been deprecated on iOS 13.0: @available(iOS, introduced: 2.0, deprecated: 13.0, message: "Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes")Piccard
E
42

For an Objective-C solution

+ (UIWindow *)keyWindow
{
    NSArray<UIWindow *> *windows = [[UIApplication sharedApplication] windows];
    for (UIWindow *window in windows) {
        if (window.isKeyWindow) {
            return window;
        }
    }
    return nil;
}
Ephod answered 18/10, 2019 at 9:4 Comment(1)
Don't forget to add nullable to the header declaration!Utas
R
42

Usually use

Swift 5

UIApplication.shared.windows.filter {$0.isKeyWindow}.first

In addition,in the UIViewController:

self.view.window

view.window is current window for scenes

WWDC 2019: enter image description here

Key Windows

  • Track windows manually
Ramentum answered 6/4, 2020 at 14:36 Comment(0)
J
18

A UIApplication extension:

extension UIApplication {
    
    /// The app's key window.
    var keyWindowInConnectedScenes: UIWindow? {
        let windowScenes: [UIWindowScene] = connectedScenes.compactMap({ $0 as? UIWindowScene })
        let windows: [UIWindow] = windowScenes.flatMap({ $0.windows })
        return windows.first(where: { $0.isKeyWindow })
    }
    
}

Usage:

let myKeyWindow: UIWindow? = UIApplication.shared.keyWindowInConnectedScenes
Jasmin answered 31/10, 2019 at 15:42 Comment(0)
E
10

Ideally, since it has been deprecated I would advice you to store the window in the SceneDelegate. However if you do want a temporary workaround, you can create a filter and retrieve the keyWindow just like this.

let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
Enos answered 26/9, 2019 at 1:56 Comment(1)
This should be a comment or edit to matt's answer, not a separate answerUtas
M
10

If you want to use it in any ViewController then you can simply use.

self.view.window
Manhandle answered 8/8, 2020 at 14:15 Comment(0)
R
10

Supports iOS 13 and later.

To keep using similar syntax as the older iOS versions UIApplication.shared.keyWindow create this extension:

extension UIApplication {
    var mainKeyWindow: UIWindow? {
        get {
            if #available(iOS 13, *) {
                return connectedScenes
                    .flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
                    .first { $0.isKeyWindow }
            } else {
                return keyWindow
            }
        }
    }
}

Usage

if let keyWindow = UIApplication.shared.mainKeyWindow {
    // Do Stuff
}
Rub answered 4/4, 2022 at 22:32 Comment(2)
Why I am getting nil in keyWindow when I am writing code as it is? My iOS version is 15.5.Hacking
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
B
9

(Tested with iOS 15.2 running on Xcode 13.2.1)

extension UIApplication {
    
    var keyWindow: UIWindow? {
        // Get connected scenes
        return self.connectedScenes
            // Keep only active scenes, onscreen and visible to the user
            .filter { $0.activationState == .foregroundActive }
            // Keep only the first `UIWindowScene`
            .first(where: { $0 is UIWindowScene })
            // Get its associated windows
            .flatMap({ $0 as? UIWindowScene })?.windows
            // Finally, keep only the key window
            .first(where: \.isKeyWindow)
    }
    
}

If you want to find the presented UIViewController in the key UIWindow, here is another extension you could find useful:

extension UIApplication {
    
    var keyWindowPresentedController: UIViewController? {
        var viewController = self.keyWindow?.rootViewController
        
        // If root `UIViewController` is a `UITabBarController`
        if let presentedController = viewController as? UITabBarController {
            // Move to selected `UIViewController`
            viewController = presentedController.selectedViewController
        }
        
        // Go deeper to find the last presented `UIViewController`
        while let presentedController = viewController?.presentedViewController {
            // If root `UIViewController` is a `UITabBarController`
            if let presentedController = presentedController as? UITabBarController {
                // Move to selected `UIViewController`
                viewController = presentedController.selectedViewController
            } else {
                // Otherwise, go deeper
                viewController = presentedController
            }
        }
        
        return viewController
    }
    
}

You can put this wherever you want, but I personally added it as an extension to UIViewController.

This allows me to add more useful extensions, like ones to present UIViewControllers more easily for example:

extension UIViewController {
    
    func presentInKeyWindow(animated: Bool = true, completion: (() -> Void)? = nil) {
        DispatchQueue.main.async {
            UIApplication.shared.keyWindow?.rootViewController?
                .present(self, animated: animated, completion: completion)
        }
    }
    
    func presentInKeyWindowPresentedController(animated: Bool = true, completion: (() -> Void)? = nil) {
        DispatchQueue.main.async {
            UIApplication.shared.keyWindowPresentedController?
                .present(self, animated: animated, completion: completion)
        }
    }
    
}
Buddha answered 7/1, 2022 at 20:47 Comment(2)
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
You're right, this solution wasn't intended for multi-scene applications. If you want to correct it, feel free.Raychel
W
7

try with that:

UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController!.present(alert, animated: true, completion: nil)
Welldefined answered 1/3, 2020 at 15:22 Comment(1)
This should be a comment or edit to matt's answer, not a separate answerUtas
P
7

As many of developers asking for Objective C code of this deprecation's replacement. You can use this below code to use the keyWindow.

+(UIWindow*)keyWindow {
    UIWindow        *windowRoot = nil;
    NSArray         *windows = [[UIApplication sharedApplication]windows];
    for (UIWindow   *window in windows) {
        if (window.isKeyWindow) {
            windowRoot = window;
            break;
        }
    }
    return windowRoot;
}

I created and added this method in the AppDelegate class as a class method and use it with very simple way that is below.

[AppDelegate keyWindow];

Don't forget to add this method in AppDelegate.h class like below.

+(UIWindow*)keyWindow;
Pforzheim answered 21/5, 2020 at 11:52 Comment(0)
G
5

For an Objective-C solution too

@implementation UIWindow (iOS13)

+ (UIWindow*) keyWindow {
   NSPredicate *isKeyWindow = [NSPredicate predicateWithFormat:@"isKeyWindow == YES"];
   return [[[UIApplication sharedApplication] windows] filteredArrayUsingPredicate:isKeyWindow].firstObject;
}

@end
Gourmandise answered 8/5, 2020 at 3:51 Comment(0)
F
3

Inspired by the answer of berni

let keyWindow = Array(UIApplication.shared.connectedScenes)
        .compactMap { $0 as? UIWindowScene }
        .flatMap { $0.windows }
        .first(where: { $0.isKeyWindow })
Furlough answered 2/1, 2020 at 14:23 Comment(3)
I checked this code in a very weird way, and it worked better than the rest. Where others were crashing, it worked regardless of foreground/background...Perfunctory
Since connectedScenes is a set, is converting to an array necessary here?Verditer
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
S
3

I've solved with:

let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
Stonedeaf answered 14/4, 2022 at 7:23 Comment(1)
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
E
3

As you probably know, the key window is deprecated because of possible multiple scenes. The most convenient solution is to provide a currentWindow as an extension, then search-and-replace.

extension UIApplication {
    var currentWindow: UIWindow? {
        connectedScenes
            .compactMap { $0 as? UIWindowScene }
            .flatMap { $0.windows }
            .first { $0.isKeyWindow }
    }
}
Etra answered 7/6, 2022 at 5:27 Comment(1)
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
S
2
NSSet *connectedScenes = [UIApplication sharedApplication].connectedScenes;
for (UIScene *scene in connectedScenes) {
    if (scene.activationState == UISceneActivationStateForegroundActive && [scene isKindOfClass:[UIWindowScene class]]) {
        UIWindowScene *windowScene = (UIWindowScene *)scene;
        for (UIWindow *window in windowScene.windows) {
            UIViewController *viewController = window.rootViewController;
            // Get the instance of your view controller
            if ([viewController isKindOfClass:[YOUR_VIEW_CONTROLLER class]]) {
                // Your code here...
                break;
            }
        }
    }
}
Schumer answered 5/12, 2019 at 9:22 Comment(1)
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
S
2

Berni's code is nice but it doesn't work when the app comes back from background.

This is my code:

class var safeArea : UIEdgeInsets
{
    if #available(iOS 13, *) {
        var keyWindow = UIApplication.shared.connectedScenes
                .filter({$0.activationState == .foregroundActive})
                .map({$0 as? UIWindowScene})
                .compactMap({$0})
                .first?.windows
                .filter({$0.isKeyWindow}).first
        // <FIX> the above code doesn't work if the app comes back from background!
        if (keyWindow == nil) {
            keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
        }
        return keyWindow?.safeAreaInsets ?? UIEdgeInsets()
    }
    else {
        guard let keyWindow = UIApplication.shared.keyWindow else { return UIEdgeInsets() }
        return keyWindow.safeAreaInsets
    }
}
Superload answered 25/1, 2021 at 10:31 Comment(2)
The part about not working when the app comes back from the background just bit me in production. In the debugger, it always returns a window, but when run manually, it fails on a regular basis when the app is launched from the backgroundTabby
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
P
1
- (UIWindow *)mainWindow {
    NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator];
    for (UIWindow *window in frontToBackWindows) {
        BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen;
        BOOL windowIsVisible = !window.hidden && window.alpha > 0;
        BOOL windowLevelSupported = (window.windowLevel >= UIWindowLevelNormal);
        BOOL windowKeyWindow = window.isKeyWindow;
        if(windowOnMainScreen && windowIsVisible && windowLevelSupported && windowKeyWindow) {
            return window;
        }
    }
    return nil;
}
Pizor answered 7/6, 2020 at 12:12 Comment(0)
H
1

I faced the issue when .foregroundActive scenes were empty

So here is my workaround

public extension UIWindow {
    @objc
    static var main: UIWindow {
        // Here we sort all the scenes in order to work around the case
        // when no .foregroundActive scenes available and we need to look through
        // all connectedScenes in order to find the most suitable one
        let connectedScenes = UIApplication.shared.connectedScenes
            .sorted { lhs, rhs in
                let lhs = lhs.activationState
                let rhs = rhs.activationState
                switch lhs {
                case .foregroundActive:
                    return true
                case .foregroundInactive:
                    return rhs == .background || rhs == .unattached
                case .background:
                    return rhs == .unattached
                case .unattached:
                    return false
                @unknown default:
                    return false
                }
            }
            .compactMap { $0 as? UIWindowScene }

        guard connectedScenes.isEmpty == false else {
            fatalError("Connected scenes is empty")
        }
        let mainWindow = connectedScenes
            .flatMap { $0.windows }
            .first(where: \.isKeyWindow)

        guard let window = mainWindow else {
            fatalError("Couldn't get main window")
        }
        return window
    }
}
Harlamert answered 4/8, 2021 at 11:18 Comment(1)
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
E
1

If your app has not been updated to adopt the Scene based app lifecycle, another simple way to get the active window object is via UIApplicationDelegate:

let window = UIApplication.shared.delegate?.window
let rootViewController = window??.rootViewController
Encomiast answered 18/9, 2021 at 9:40 Comment(0)
B
1

if you're using SwiftLint with 'first_where' rule and wanna to silence warring:

UIApplication.shared.windows.first(where: { $0.isKeyWindow })
Bonedry answered 8/1, 2022 at 20:41 Comment(1)
windows is deprecated in iOS 15. Ref: @available(iOS, introduced: 2.0, deprecated: 15.0, message: "Use UIWindowScene.windows on a relevant window scene instead")Nameplate
C
1

An Objective C solution:

UIWindow *foundWindow = nil;
NSSet *scenes=[[UIApplication sharedApplication] connectedScenes];
NSArray *windows;
for(id aScene in scenes){  // it's an NSSet so you can't use the first object
    windows=[aScene windows];
    if([aScene activationState]==UISceneActivationStateForegroundActive)
         break;
}
for (UIWindow  *window in windows) {
    if (window.isKeyWindow) {
        foundWindow = window;
        break;
    }
}
 // and to find the parent viewController:
UIViewController* parentController = foundWindow.rootViewController;
while( parentController.presentedViewController &&
      parentController != parentController.presentedViewController ){
    parentController = parentController.presentedViewController;
}
Crisper answered 7/12, 2022 at 22:3 Comment(0)
A
0

I alloc'ed a newWindow for a view, and set it [newWindow makeKeyAndVisible]; When finished using it, set it [newWindow resignKeyWindow]; and then try to show the original key-window directly by [UIApplication sharedApplication].keyWindow.

Everything is all right on iOS 12, but on iOS 13 the original key-window can't been normal shown. It shows a whole white screen.

I solved this problem by:

UIWindow *mainWindow = nil;
if ( @available(iOS 13.0, *) ) {
   mainWindow = [UIApplication sharedApplication].windows.firstObject;
   [mainWindow makeKeyWindow];
} else {
    mainWindow = [UIApplication sharedApplication].keyWindow;
}
Adore answered 25/10, 2019 at 7:55 Comment(0)
C
0

My solution is the following, works in iOS 15

let window = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first
Carolinian answered 27/12, 2022 at 13:38 Comment(1)
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
B
0
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
    )
}
Ballard answered 3/3, 2023 at 14:47 Comment(1)
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
E
0
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
    }
    
    static func setRootViewVC(_ viewController: UIViewController){
        UIApplication.shared.currentUIWindow()?.rootViewController = viewController
    }
}

so we can use

func redirectingToMainScreen(){
    
    let mainVC = UIStoryboard.loadMainScreen()
    UIApplication.setRootViewVC(mainVC)
} 
Enthrone answered 23/3, 2023 at 6:22 Comment(1)
One huge issue with solutions like this is that you can easily end up accessing the wrong window when dealing with a multi-scene app on an iPad, for example. The solution should involve getting the window for the relevant scene.Monad
B
0

in my case

class func getTopViewController(base: UIViewController? = UIApplication.shared.windows.last?.rootViewController) -> UIViewController? { }
Bohol answered 7/4 at 13:26 Comment(0)
C
0

Objective C:-

Deprecated:

[UIApplication sharedApplication].windows.firstObject.rootViewController

Solution:

[[UIApplication sharedApplication] delegate].window.rootViewController

Composed answered 16/4 at 12:24 Comment(1)
Apps created after iOS 13 shouldn't use the window property of UIApplicationDelegate because apps that support scenes can have more than one window.Monad
G
-2

For iOS 16, I used the following:

let keyWindow = UIApplication.shared.currentUIWindow()?.windowScene?.keyWindow
Graphy answered 23/9, 2022 at 3:57 Comment(0)
O
-2

The below solution works for me for iOS 16.

UIApplication
            .shared
            .connectedScenes
            .flatMap({ ($0 as? UIWindowScene)?.windows ?? [] })
            .filter({$0.isKeyWindow})
            .first
Obstetric answered 13/6, 2023 at 5:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.