如何解决:iOS 13.0中不推荐使用“ keyWindow”


114

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

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

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

我该如何显示对话框?


您是在SceneDelegate还是在这样做AppDelegate?而且,您能再发布一些代码以便我们重复吗?
dfd

1
iOS中不再存在“ keyWindow”概念,因为单个应用程序可以具有多个窗口。您可以将创建的窗口存储在自己的窗口中SceneDelegate(如果使用的话SceneDelegate
Sudara

1
@Sudara:所以,如果我还没有视图控制器,但想显示一个警报-如何在场景中执行呢?如何获取场景,以便可以检索其rootViewController?(因此,简而言之:场景相当于UIApplication的“共享”是什么?)
Hardy

Answers:


95

这是我的解决方案:

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

用法,例如:

keyWindow?.endEditing(true)

4
谢谢-不是很直观的东西... 8
哈迪

同时,我使用多场景示例(developer.apple.com/documentation/uikit/app_and_environment/…)测试了该方法,所有方法均按预期工作。
伯尼

1
测试一下 activationStateforegroundInactive此处值,在我的测试中,如果出现警报,就是这种情况。
德鲁

1
@Drew应该进行测试,因为在应用程序启动时,视图控制器已经可见,但状态为 foregroundInactive
Gargo

2
这段代码为我生成keyWindow = nil。matt解决方案是可行的。

169

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

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

我还要提醒您,keyWindow不要过分重视弃用。完整的警告消息如下:

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

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


您将如何处理这样的问题,let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "homeVC") as! UITabBarController UIApplication.shared.keyWindow?.rootViewController = vc因为在iOS 13和卡片视图中,这将成为一个问题,因为用户说出注销后,该用户将使用视图层次结构中的主应用程序被推送到登录屏幕,在此用户可以向下滑动并返回有问题。
卢卡斯·宾巴

1
@Mario不是Windows数组中的第一个窗口。这是windows数组中的第一个关键窗口。
马特

1
@Mario但问题是前提是只有一个场景。要解决的问题仅仅是弃用某种属性。显然,如果您在iPad上实际上有多个窗口,生活将变得更加复杂!如果您真的想编写一个多窗口iPad应用程序,那么祝您好运。
马特

1
@ramzesenok当然可以更好。但这没错。相反,我是第一个建议,要求应用程序提供一个作为关键窗口的窗口可能就足够了,从而避免了弃用该keyWindow属性。因此,投票。如果您不喜欢它,请对其投票。但是不要告诉我更改它以匹配其他人的答案。正如我所说,那是错误的。
马特

5
现在,这也可以简化为UIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar

62

略微提高了matt的出色答案,这甚至更简单,更短且更优雅:

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

1
谢谢!有没有办法在目标c中做到这一点?
Allenktv

1
@Allenktv不幸的NSArray是没有等效于first(where:)。您可以尝试使用filteredArrayUsingPredicate:和构成单线firstObject:
pommy

1
@Allenktv在注释部分中弄乱了代码,因此我在下面发布了一个Objective-C等效项。
user2002649

Xcode 11.2编译器报告了该错误,并建议添加括号及其内容至first(where:)UIApplication.shared.windows.first(where: { $0.isKeyWindow })
Yassine ElBadaoui

1
现在,这也可以简化为UIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar

25

这是一种向后兼容的检测方式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
}

2
这是最优雅的答案,它展示了Swift extension的美丽之处。🙂
克利夫顿拉布兰

1
可用性检查是几乎没有必要,因为windowsisKeyWindow自奥斯2.0已经出现,并且first(where:)因为Xcode的9.0 /夫特4/2017
pommy

UIApplication.keyWindow在iOS 13.0上已被弃用:@available(iOS,已引入:2.0,已弃用:13.0,消息:“不应用于支持多个场景的应用程序,因为它会返回所有已连接场景的关键窗口”)
Vadim Bulavin


14

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

10

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

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

9

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

2

试试看:

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

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

1

对于Objective-C解决方案

@implementation UIWindow (iOS13)

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

@end

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


0

berni的答案启发

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

0

正如许多开发人员要求使用此弃用替代品的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;

-1

我遇到了同样的问题。我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;
}

希望能帮助到你。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.