我使用核心数据与云工具包,因此要检查iCloud用户状态在应用程序启动。如果出现问题,我想向用户发出一个对话框,我使用UIApplication.shared.keyWindow?. rootviewcontroller ?.present(…)到目前为止。

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

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

我应该如何呈现对话呢?


这是我的解决方案:

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

使用如:

keyWindow?.endEditing(true)

我在这里提出的建议在iOS 15中已弃用。现在怎么办?好吧,如果一个应用没有自己的多个窗口,我假设接受的现代方式将是获得应用的第一个connectedscene,强制到一个UIWindowScene,并获得它的第一个窗口。但这几乎正是公认的答案所做的!所以我的变通办法在这一点上感觉相当无力。但是,由于历史原因,我还是让它保持原样。


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

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

我还要提醒大家,不要过于认真地对待keyWindow的弃用。完整的警告信息如下:

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

所以如果你不支持iPad上的多窗口,继续使用keyWindow是没有异议的。


iOS 16,兼容至iOS 15

由于这个帖子在三年后仍有流量,我想分享我认为目前功能最优雅的解决方案。它也适用于SwiftUI。

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

iOS 15和16,兼容至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指出这一点!

最初的回答:

在matt的精彩回答基础上稍作改进,这个回答更简单、更简短、更优雅:

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

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

let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

对于Objective-C解决方案

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

我为一个视图分配了一个新窗口,并设置它[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;
}

一个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

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

灵感来自berni的回答

let keyWindow = Array(UIApplication.shared.connectedScenes)
        .compactMap { $0 as? UIWindowScene }
        .flatMap { $0.windows }
        .first(where: { $0.isKeyWindow })

下面是一个向后兼容的检测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
}

试试这个:

UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController!.present(alert, animated: true, completion: nil)

通常使用

斯威夫特5

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

另外,在UIViewController中:

self.view.window

视图。窗口是当前场景的窗口

WWDC 2019:

关键的窗口 手动跟踪窗口

在iPad上介绍多个Windows - WWDC 2019 -视频-苹果开发人员 支持iPad上的多个Windows |苹果开发者文档


对于Objective-C解决方案也是如此

@implementation UIWindow (iOS13)

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

@end

由于许多开发人员要求将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;

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

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

self.view.window

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

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

如果你的应用还没有更新到采用基于场景的应用生命周期,另一种获得活动窗口对象的简单方法是通过UIApplicationDelegate:

let window = UIApplication.shared.delegate?.window
let rootViewController = window??.rootViewController

(在运行于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)
    }
    
}

如果你想在关键的UIWindow中找到呈现的UIViewController,这是另一个你可以发现有用的扩展:

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

你可以把它放在任何你想要的地方,但我个人把它作为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)
        }
    }
    
}

如果你使用SwiftLint的'first_where'规则,并想静音交战:

UIApplication.shared.windows.first(where: { $0.isKeyWindow })

支持iOS 13及以上版本。

为了继续使用与旧iOS版本相似的语法,UIApplication.shared.keyWindow创建以下扩展:

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

使用

if let keyWindow = UIApplication.shared.mainKeyWindow {
    // Do Stuff
}

我已经解决了:

let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first

您可能知道,由于可能存在多个场景,因此不建议使用键窗口。最方便的解决方案是提供一个currentWindow作为扩展,然后进行搜索和替换。

extension UIApplication {
    var currentWindow: UIWindow? {
        connectedScenes
            .compactMap { $0 as? UIWindowScene }
            .flatMap { $0.windows }
            .first { $0.isKeyWindow }
    }
}

对于iOS 16,我使用了以下方法:

let keyWindow = UIApplication.shared.currentUIWindow()?.windowScene?.keyWindow

Objective C解决方案:

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

我的解决方案如下,适用于iOS 15

let window = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first