339

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

How shall I present the dialog instead?

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Hardy
  • 4,344
  • 3
  • 17
  • 27
  • Are you doing this in `SceneDelegate` or `AppDelegate`? And, could you post a bit more code so we can duplicate? –  Jul 21 '19 at 15:38
  • 2
    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`) – Sudara Jul 22 '19 at 03:34
  • 2
    @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?) – Hardy Jul 22 '19 at 12:20

30 Answers30

423

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.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 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. – Lukas Bimba Sep 25 '19 at 02:26
  • @LukasBimba That seems unrelated; could you ask it as a separate question? That will make it easier to help. Thanks. – matt Sep 25 '19 at 20:44
  • Here is the question: https://stackoverflow.com/questions/58151928/swift-5-ios13-segue-to-another-storyboard-or-unconnected-view-controller-witho – Lukas Bimba Sep 29 '19 at 03:11
  • 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? – Rakesha Shastri Oct 10 '19 at 09:15
  • @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). – matt Oct 10 '19 at 12:05
  • @matt 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. – Rakesha Shastri Oct 10 '19 at 12:09
  • @RakeshaShastri Could you ask about this as a separate question? Comments are not the place to iron this out. – matt Oct 10 '19 at 13:02
  • I will as soon as i get time to make a simple, complete, verifiable example. – Rakesha Shastri Oct 10 '19 at 13:08
  • 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? – joliejuly Nov 28 '19 at 10:11
  • 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? – Mario Feb 03 '20 at 17:00
  • 2
    @Mario It's not the first window in the windows array. It's the first _key_ window in the windows array. – matt Feb 03 '20 at 17:20
  • 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! – Mario Feb 03 '20 at 19:25
  • 2
    @Mario 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. – matt Feb 03 '20 at 19:46
  • consider using first(where:) as it is more efficient than your solution (it doesn't create an intermediate array of filtered items) – ramzesenok Feb 22 '20 at 12:57
  • 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 better – ramzesenok Feb 22 '20 at 16:53
  • 2
    @ramzesenok 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. – matt Feb 22 '20 at 17:06
  • @Mario 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. – andreas1724 Jun 03 '20 at 22:58
  • 49
    This now can also be simplified as `UIApplication.shared.windows.first(where: \.isKeyWindow)` – dadalar Jun 04 '20 at 08:20
  • 2
    @dadalar Yes, I really like that syntax (new in Swift 5.2). – matt Jul 21 '20 at 19:11
  • @LukasBimba you can replace your code to this `UIApplication.shared.windows.first?.rootViewController = vc` or simply for this one `self.view.window?.rootViewController = vc` – Carlos Irano Aug 30 '20 at 23:47
  • The accepted answer is actually not working for me in cases where it's called during `applicationWillEnterForeground`. The solution @matt proposes, works. – Martin Sep 09 '20 at 02:24
  • @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. – pommy Sep 20 '20 at 08:01
  • @pommy The core content remained the same (only performance changed, not behavior nor approach), and [my style](https://swift-style-guidelines.bhstudios.org/Whitespace/#chopping-down-functions) 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.](https://stackoverflow.com/help/editing) For us with edit privilege, [editing is encouraged!](https://stackoverflow.com/help/privileges/edit) – Ky - Sep 23 '20 at 15:51
  • @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. – matt Sep 23 '20 at 16:12
  • 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. – Ky - Sep 23 '20 at 16:18
  • 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: https://meta.stackoverflow.com/q/401472/3939277 – Ky - Sep 23 '20 at 16:58
  • @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. – matt Sep 23 '20 at 17:08
  • @matt thanks a lot, your simple solution solved my great problem! :) – Ricardo Barroso May 11 '21 at 10:45
  • 1
    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: https://tengl.net/blog/2021/11/9/uiapplication-key-window-replacement But you are mostly correct. Your answer should be one up top. – Teng L Nov 09 '21 at 17:49
  • 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 `` – Jadian Aug 19 '22 at 07:23
  • @Jadian 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://stackoverflow.com/a/57169802/341994 so there's little point commenting on my answer. – matt Aug 19 '22 at 13:20
  • a bit shorter: `UIApplication.shared.windows.first(where: { $0.isKeyWindow })` – Ozgur Vatansever Aug 22 '22 at 14:41
  • @OzgurVatansever It hardly matters, because that expression is deprecated. – matt Aug 22 '22 at 14:44
  • How about using this to access first key window `UIApplication.shared.firstKeyWindow?` – Surendra Kumar Jan 05 '23 at 15:06
  • @SurendraKumar That would be cool if `firstKeyWindow` existed. – matt Jan 05 '23 at 15:12
  • 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 }` – salami Feb 13 '23 at 20:16
  • @salami I don't think that would be a wise approach. – matt Feb 13 '23 at 20:23
298

iOS 16, 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.

pommy
  • 3,717
  • 1
  • 15
  • 25
  • 1
    Thank you! Is there a way to do this in objective c? – Allenktv Sep 21 '19 at 13:21
  • 1
    @Allenktv Unfortunately `NSArray` doesn’t have an equivalent to `first(where:)`. You may try to compose a one-liner with `filteredArrayUsingPredicate:` and `firstObject:`. – pommy Sep 22 '19 at 15:23
  • 1
    @Allenktv the code got mangled in the comments section, so I posted an Objective-C equivalent below. – user2002649 Oct 18 '19 at 09:01
  • 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 })` – Yassine ElBadaoui Nov 03 '19 at 03:44
  • @YassineElBadaoui The parenthesis is not necessary in a simple assignment 89 – pommy Nov 04 '19 at 08:12
  • 12
    This now can also be simplified as `UIApplication.shared.windows.first(where: \.isKeyWindow)` – dadalar Jun 04 '20 at 08:20
  • @dadalar 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. – pommy Jun 05 '20 at 11:52
  • @pommy 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. – dadalar Jun 05 '20 at 11:58
  • This should have been posted as an edit to Matt's answer – Ky - Sep 17 '20 at 21:31
  • 6
    Unfortunately `windows` is now deprecated too. – matt Sep 25 '21 at 12:47
  • 1
    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) – Teng L Nov 09 '21 at 17:09
  • 1
    @TengL you can simply use `filter(\.isKeyWindow)` instead of `first { $0.isKeyWindow }` and return an array with all key windows – Leo Dabus Mar 12 '22 at 14:34
  • '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?? – PRakash Jul 22 '22 at 08:59
  • @PRakash The answer for iOS 15 derives the windows from those of the connected scenes of the shared UIApplication that are of type UIWindowScene. – pommy Jul 22 '22 at 14:29
  • 1
    Returns nil on iOS 16 – Deepak Sharma Sep 28 '22 at 08:24
  • @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. – pommy Sep 29 '22 at 09:56
  • 1
    @pommy Thanks for keeping this answer updated! Unfortunately I can't upvote it a second time :) – matt Jan 05 '23 at 15:13
  • 2
    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. – Rob Apr 05 '23 at 15:58
  • @Rob 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. – pommy Apr 06 '23 at 16:35
  • @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. – pommy Apr 06 '23 at 17:07
  • 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. – HangarRash Apr 06 '23 at 17:11
  • 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](https://developer.apple.com/wwdc19/259). 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.) – Rob Apr 06 '23 at 22:53
219

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)
Guillaume Algis
  • 10,705
  • 6
  • 44
  • 72
berni
  • 2,673
  • 1
  • 12
  • 10
  • Meanwhile I tested the approach with the multiple scene sample (https://developer.apple.com/documentation/uikit/app_and_environment/scenes/supporting_multiple_windows_on_ipad) and all worked as expected. – berni Jul 24 '19 at 08:27
  • 1
    You just need the get `isKeyWindow`. – NSProgrammer Sep 17 '19 at 16:26
  • 4
    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. – Drew Dec 20 '19 at 04:05
  • 2
    @Drew it should be tested because on app start the view controller is already visible but the state is `foregroundInactive` – Gargo Feb 01 '20 at 20:17
  • 11
    This code produces keyWindow = nil for me. `matt` solution is the one that works. – Duck Feb 25 '20 at 15:36
  • 3
    This solution is actually not working for me in cases where it's called during applicationWillEnterForeground. The solution @matt proposes, works. – Martin Sep 09 '20 at 02:25
  • 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! – Ky - Sep 16 '20 at 21:43
  • `.map({$0 as? UIWindowScene}).compactMap({$0})` can be replaced with `.compactMap { $0 as? UIWindowScene }` – Jordan H Jul 24 '21 at 18:41
  • 1
    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: https://tengl.net/blog/2021/11/9/uiapplication-key-window-replacement – Teng L Nov 09 '21 at 17:48
  • true also somtimes I have nil trying to access this way – Michał Ziobro Mar 02 '22 at 14:44
  • 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. – HangarRash Apr 06 '23 at 17:12
  • This didn't work for me, but pommy solution did! Thanks! – Endy Silveira May 26 '23 at 19:59
61

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
}
Vadim Bulavin
  • 3,697
  • 25
  • 19
  • 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. – pommy Jun 05 '20 at 12:02
  • 1
    `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")_ – Vadim Bulavin Jun 09 '20 at 07:54
  • @VadimBulavin you did not understand the comment pommy suggested only using`static var key: UIWindow? { UIApplication.shared.windows.first(where: \.isKeyWindow) }` – Leo Dabus Jun 03 '21 at 20:22
40

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;
}
Johannes Fahrenkrug
  • 42,912
  • 19
  • 126
  • 165
user2002649
  • 710
  • 5
  • 9
40

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
iHTCboy
  • 2,715
  • 21
  • 20
15

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
Daniel Storm
  • 18,301
  • 9
  • 84
  • 152
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
Chaitanya Ramji
  • 271
  • 3
  • 8
  • 1
    This should be a comment or edit to [matt's answer](https://stackoverflow.com/a/57899013/3939277), not a separate answer – Ky - Sep 18 '20 at 16:22
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
}
Andi Beqiri
  • 131
  • 1
  • 4
  • Why I am getting nil in keyWindow when I am writing code as it is? My iOS version is 15.5. – Developer Sep 26 '22 at 12:17
  • 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. – HangarRash Apr 06 '23 at 17:13
9

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

self.view.window
Simran Singh
  • 445
  • 6
  • 8
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)
        }
    }
    
}
Rémi B.
  • 2,410
  • 11
  • 17
  • 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. – HangarRash Apr 06 '23 at 17:13
  • You're right, this solution wasn't intended for multi-scene applications. If you want to correct it, feel free. – Rémi B. Jul 11 '23 at 15:01
7

try with that:

UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController!.present(alert, animated: true, completion: nil)
Anas
  • 73
  • 1
  • 3
  • This should be a comment or edit to [matt's answer](https://stackoverflow.com/a/57899013/3939277), not a separate answer – Ky - Sep 18 '20 at 16:23
6

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;
Paras Joshi
  • 20,427
  • 11
  • 57
  • 70
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
MadWai
  • 51
  • 1
  • 2
3

Inspired by the answer of berni

let keyWindow = Array(UIApplication.shared.connectedScenes)
        .compactMap { $0 as? UIWindowScene }
        .flatMap { $0.windows }
        .first(where: { $0.isKeyWindow })
Milander
  • 1,021
  • 11
  • 18
  • 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... – shanezzar Sep 18 '21 at 09:52
  • Since connectedScenes is a set, is converting to an array necessary here? – Marcy Oct 29 '21 at 00:59
  • 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. – HangarRash Apr 06 '23 at 17:14
3

I've solved with:

let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
DungeonDev
  • 1,176
  • 1
  • 12
  • 16
  • 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. – HangarRash Apr 06 '23 at 17:14
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 }
    }
}
samwize
  • 25,675
  • 15
  • 141
  • 186
  • 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. – HangarRash Apr 06 '23 at 17:13
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;
            }
        }
    }
}
Atul Pol
  • 341
  • 3
  • 5
  • 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. – HangarRash Apr 06 '23 at 17:14
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
    }
}
PerfectGamesOnline.com
  • 1,774
  • 1
  • 18
  • 31
  • 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 background – otusweb Sep 22 '21 at 08:29
  • 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. – HangarRash Apr 06 '23 at 17:14
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;
}
Erfan
  • 1,284
  • 1
  • 13
  • 21
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
    }
}
Ivan Smetanin
  • 1,999
  • 2
  • 21
  • 28
  • 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. – HangarRash Apr 06 '23 at 17:14
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
Junfeng
  • 940
  • 10
  • 19
1

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

UIApplication.shared.windows.first(where: { $0.isKeyWindow })
polykuzin
  • 21
  • 5
  • 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")` – Abdurrahman Mubeen Ali Apr 29 '22 at 15:07
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;
}
Peter B. Kramer
  • 16,385
  • 1
  • 16
  • 20
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;
}
TylerH
  • 20,799
  • 66
  • 75
  • 101
coderChrisLee
  • 289
  • 3
  • 6
0

My solution is the following, works in iOS 15

let window = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first
Stan S.
  • 237
  • 7
  • 22
  • 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. – HangarRash Apr 06 '23 at 17:15
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
    )
}
Olcay Ertaş
  • 5,987
  • 8
  • 76
  • 112
Mohammad Razipour
  • 3,643
  • 3
  • 29
  • 49
  • 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. – HangarRash Apr 06 '23 at 17:15
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)
} 
Sahil Omer
  • 163
  • 9
  • 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. – HangarRash Apr 06 '23 at 17:15
-1

For iOS 16, I used the following:

let keyWindow = UIApplication.shared.currentUIWindow()?.windowScene?.keyWindow
syedfa
  • 2,801
  • 1
  • 41
  • 74
-1

The below solution works for me for iOS 16.

UIApplication
            .shared
            .connectedScenes
            .flatMap({ ($0 as? UIWindowScene)?.windows ?? [] })
            .filter({$0.isKeyWindow})
            .first
Ashok
  • 55
  • 1
  • 10