253

我将 Core Data 与 Cloud Kit 一起使用,因此必须在应用程序启动期间检查 iCloud 用户状态。如果出现问题,我想向用户发出一个对话框,并且我一直使用它UIApplication.shared.keyWindow?.rootViewController?.present(...)

在 Xcode 11 beta 4 中,现在有一条新的弃用消息,告诉我:

'keyWindow' 在 iOS 13.0 中已弃用:不应用于支持多个场景的应用程序,因为它会在所有连接的场景中返回一个关键窗口

我应该如何呈现对话框?

4

21 回答 21

362

编辑我在这里提出的建议在 iOS 15 中已被弃用。那么现在呢?好吧,如果一个应用程序没有自己的多个窗口,我认为公认的现代方式是获取应用程序的第一个connectedScenes,强制到 UIWindowScene,然后获取它的第一个窗口。但这几乎正是公认的答案所做的!所以我的解决方法在这一点上感觉相当微弱。但是,出于历史原因,我会保留它。


公认的答案虽然巧妙,但可能过于详尽。您可以更简单地获得完全相同的结果:

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

我还警告说,不keyWindow应该过分认真对待弃用 。完整的警告信息如下:

'keyWindow' 在 iOS 13.0 中已弃用:不应用于支持多个场景的应用程序,因为它会在所有连接的场景中返回一个关键窗口

因此,如果您不支持 iPad 上的多个窗口,则不反对继续使用keyWindow.

于 2019-09-12T02:40:24.717 回答
178

这是我的解决方案:

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

用法例如:

keyWindow?.endEditing(true)
于 2019-07-23T17:58:26.977 回答
157

iOS 15,兼容至 iOS 13

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

请注意,这connectedScenes仅在 iOS 13 之后可用。如果您需要支持早期版本的 iOS,则必须将其放在if #available(iOS 13, *)声明中。

更长但更容易理解的变体:

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

iOS 13 和 14

以下历史答案在 iOS 15 上仍然有效,但应替换,因为UIApplication.shared.windows已弃用。感谢@matt 指出这一点!

原答案:

稍微改进了马特的优秀答案,这更简单,更短,更优雅:

UIApplication.shared.windows.first { $0.isKeyWindow }
于 2019-09-20T16:10:39.003 回答
54

这是一种向后兼容的检测方法keyWindow

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

用法:

if let keyWindow = UIWindow.key {
    // Do something
}
于 2020-01-29T12:30:05.620 回答
38

通常使用

斯威夫特 5

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

另外,在 UIViewController 中:

self.view.window

view.window是场景的当前窗口

世界开发者大会 2019: 在此处输入图像描述

关键窗口

  • 手动跟踪窗口
于 2020-04-06T14:36:19.060 回答
29

对于 Objective-C 解决方案

+(UIWindow*)keyWindow
{
    UIWindow        *foundWindow = nil;
    NSArray         *windows = [[UIApplication sharedApplication]windows];
    for (UIWindow   *window in windows) {
        if (window.isKeyWindow) {
            foundWindow = window;
            break;
        }
    }
    return foundWindow;
}
于 2019-10-18T09:04:19.747 回答
15

UIApplication扩展:

extension UIApplication {

    /// The app's key window taking into consideration apps that support multiple scenes.
    var keyWindowInConnectedScenes: UIWindow? {
        return windows.first(where: { $0.isKeyWindow })
    }

}

用法:

let myKeyWindow: UIWindow? = UIApplication.shared.keyWindowInConnectedScenes
于 2019-10-31T15:42:36.553 回答
10

理想情况下,由于它已被弃用,我建议您将窗口存储在 SceneDelegate 中。但是,如果您确实需要临时解决方法,则可以像这样创建一个过滤器并检索 keyWindow。

let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
于 2019-09-26T01:56:37.510 回答
8

如果你想在任何 ViewController 中使用它,那么你可以简单地使用。

self.view.window
于 2020-08-08T14:15:20.377 回答
6

试试看:

UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController!.present(alert, animated: true, completion: nil)
于 2020-03-01T15:22:46.300 回答
4

也适用于 Objective-C 解决方案

@implementation UIWindow (iOS13)

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

@end
于 2020-05-08T03:51:23.590 回答
4

正如许多开发人员要求替换此弃用的Objective C代码一样。您可以使用下面的代码来使用 keyWindow。

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

我在类中创建并添加了这个方法AppDelegate作为类方法,并以下面非常简单的方式使用它。

[AppDelegate keyWindow];

不要忘记在 AppDelegate.h 类中添加此方法,如下所示。

+(UIWindow*)keyWindow;
于 2020-05-21T11:52:51.753 回答
3

灵感来自berni的回答

let keyWindow = Array(UIApplication.shared.connectedScenes)
        .compactMap { $0 as? UIWindowScene }
        .flatMap { $0.windows }
        .first(where: { $0.isKeyWindow })
于 2020-01-02T14:23:08.993 回答
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;
            }
        }
    }
}
于 2019-12-05T09:22:44.183 回答
2

Berni 的代码很好,但是当应用程序从后台返回时它不起作用。

这是我的代码:

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
    }
}
于 2021-01-25T10:31:26.240 回答
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;
}
于 2020-06-07T12:12:36.197 回答
1

如果您的应用尚未更新为采用基于场景的应用生命周期,另一种获取活动窗口对象的简单方法是通过UIApplicationDelegate

let window = UIApplication.shared.delegate?.window
let rootViewController = window??.rootViewController
于 2021-09-18T09:40:57.477 回答
1

我已经在重复的提要上回答了这个问题,由于我在这里找不到提供尽可能多代码的答案(已评论),所以这是我的贡献:

(在 Xcode 13.2.1 上运行的 iOS 15.2 测试)

extension UIApplication {
    
    var keyWindow: UIWindow? {
        // Get connected scenes
        return UIApplication.shared.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)
    }
    
}

如果您想找到UIViewControllerkey 中显示的UIWindow 内容,这里是另一个extension您可能会发现有用的内容:

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
    }
    
}

你可以把它放在任何你想要的地方,但我个人将它添加为extensionto UIViewController

这使我可以添加更多有用的扩展,例如更容易呈现UIViewControllers 的扩展,例如:

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)
        }
    }
    
}
于 2022-01-07T20:47:28.897 回答
0

我遇到了同样的问题。我newWindow为一个视图分配了一个,并设置它[newWindow makeKeyAndVisible]; 使用完后,设置它[newWindow resignKeyWindow]; ,然后尝试直接通过显示原始键窗口[UIApplication sharedApplication].keyWindow

在 iOS 12 上一切正常,但在 iOS 13 上,原始键窗口无法正常显示。它显示一个全白的屏幕。

我通过以下方式解决了这个问题:

UIWindow *mainWindow = nil;
if ( @available(iOS 13.0, *) ) {
   mainWindow = [UIApplication sharedApplication].windows.firstObject;
   [mainWindow makeKeyWindow];
} else {
    mainWindow = [UIApplication sharedApplication].keyWindow;
}

希望能帮助到你。

于 2019-10-25T07:55:49.180 回答
0

.foregroundActive当场景为空时,我遇到了这个问题

所以这是我的解决方法

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
    }
}
于 2021-08-04T11:18:42.380 回答
0

如果您正在使用带有“first_where”规则的 SwiftLint 并想消除交战:

UIApplication.shared.windows.first(where: { $0.isKeyWindow })
于 2022-01-08T20:41:19.870 回答