如何解决:“keyWindow”在 iOS 13.0 中已弃用
Posted
技术标签:
【中文标题】如何解决:“keyWindow”在 iOS 13.0 中已弃用【英文标题】:How to resolve: 'keyWindow' was deprecated in iOS 13.0 【发布时间】:2019-11-29 17:38:49 【问题描述】:我将 Core Data 与 Cloud Kit 一起使用,因此必须在应用程序启动期间检查 iCloud 用户状态。如果出现问题,我想向用户发出一个对话框,到目前为止我使用UIApplication.shared.keyWindow?.rootViewController?.present(...)
。
在 Xcode 11 beta 4 中,现在有一条新的弃用消息,告诉我:
'keyWindow' 在 ios 13.0 中已弃用:不应用于支持多个场景的应用程序,因为它会在所有连接的场景中返回一个关键窗口
我应该如何呈现对话框?
【问题讨论】:
您是在SceneDelegate
还是AppDelegate
中执行此操作?而且,您能否发布更多代码以便我们复制?
iOS 中不再有“keyWindow”概念,因为单个应用程序可以有多个窗口。您可以将创建的窗口存储在 SceneDelegate
中(如果您使用的是 SceneDelegate
)
@Sudara:所以,如果我还没有视图控制器,但想显示警报 - 如何处理场景?如何获取场景,以便可以检索到它的 rootViewController? (所以,简而言之:什么是相当于 UIApplication 的“共享”的场景?)
【参考方案1】:
编辑 我在这里提出的建议在 iOS 15 中已被弃用。那么现在该怎么办?好吧,如果一个应用程序没有自己的多个窗口,我认为公认的现代方法是获取应用程序的第一个 connectedScenes
,强制转换为 UIWindowScene,然后获取它的第一个窗口。但这几乎正是公认的答案所做的!所以我的解决方法在这一点上感觉相当微弱。但是,出于历史原因,我会保留它。
公认的答案虽然巧妙,但可能过于详尽。您可以更简单地获得完全相同的结果:
UIApplication.shared.windows.filter $0.isKeyWindow.first
我还要提醒大家不要过分认真地对待 keyWindow
的弃用。完整的警告信息如下:
'keyWindow' 在 iOS 13.0 中已弃用:不应该用于支持多个场景的应用程序,因为它会在所有连接的场景中返回一个关键窗口
因此,如果您在 iPad 上不支持多窗口,则不反对继续使用 keyWindow
。
【讨论】:
@Mario 这不是 windows 数组中的第一个窗口。它是 windows 数组中的第一个 key 窗口。 @Mario 但问题的前提是只有一个场景。正在解决的问题仅仅是对某个属性的弃用。显然,如果您在 iPad 上确实有多个窗口,那么生活会复杂得多!如果你真的想写一个多窗口的 iPad 应用,祝你好运。 @ramzesenok 当然可以更好。但这并没有错。相反,我是第一个建议向应用程序请求一个作为关键窗口的窗口就足够了,从而避免了keyWindow
属性的弃用。因此赞成票。如果您不喜欢它,请投反对票。但是不要告诉我改变它以匹配别人的答案;正如我所说,那是错误的。
现在也可以简化为UIApplication.shared.windows.first(where: \.isKeyWindow)
@dadalar 是的,我真的很喜欢这种语法(Swift 5.2 中的新功能)。【参考方案2】:
这是我的解决方案:
let keyWindow = UIApplication.shared.connectedScenes
.filter($0.activationState == .foregroundActive)
.compactMap($0 as? UIWindowScene)
.first?.windows
.filter($0.isKeyWindow).first
用法例如:
keyWindow?.endEditing(true)
【讨论】:
谢谢 - 这不是很直观的东西... 8-) 你只需要获取isKeyWindow
。
这里也可以测试activationState
值foregroundInactive
,在我的测试中,如果出现警报就会出现这种情况。
此代码为我生成 keyWindow = nil。 matt
解决方案是可行的。
这个解决方案实际上不适用于我在 applicationWillEnterForeground 期间调用它的情况。 @matt 提出的解决方案有效。【参考方案3】:
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
【讨论】:
谢谢!有没有办法在目标 c 中做到这一点? @Allenktv 不幸的是,NSArray
没有与first(where:)
等效的版本。您可以尝试用filteredArrayUsingPredicate:
和firstObject:
组成一个单行。
@Allenktv 代码在 cmets 部分被破坏了,所以我在下面发布了一个 Objective-C 等价物。
Xcode 11.2 编译器报告了此答案的错误,并建议将括号及其内容添加到first(where:)
:UIApplication.shared.windows.first(where: $0.isKeyWindow )
现在也可以简化为UIApplication.shared.windows.first(where: \.isKeyWindow)
【参考方案4】:
这是一种向后兼容的检测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
【讨论】:
这是最优雅的答案,展示了 Swiftextension
s 的美丽。 ?
可用性检查几乎没有必要,因为 windows
和 isKeyWindow
自 iOS 2.0 以来就已存在,first(where:)
自 Xcode 9.0 / Swift 4 / 2017 以来就已存在。
UIApplication.keyWindow
已在 iOS 13.0 上弃用:@available(iOS,引入:2.0,弃用:13.0,消息:“不应用于支持多个场景的应用程序,因为它返回所有连接场景的关键窗口")
@VadimBulavin 你不明白 pommy 建议只使用static var key: UIWindow? UIApplication.shared.windows.first(where: \.isKeyWindow)
【参考方案5】:
通常使用
斯威夫特 5
UIApplication.shared.windows.filter $0.isKeyWindow.first
另外,在UIViewController中:
self.view.window
view.window
是场景的当前窗口
WWDC 2019:
Introducing Multiple Windows on iPad - WWDC 2019 - Videos - Apple Developer Supporting Multiple Windows on iPad | Apple Developer Documentation关键窗口
手动跟踪窗口
【讨论】:
【参考方案6】:对于 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;
【讨论】:
不要忘记将nullable
添加到标题声明中!
哦,我怎么不会错过 Objective-C :)【参考方案7】:
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
【讨论】:
【参考方案8】:理想情况下,由于它已被弃用,我建议您将窗口存储在 SceneDelegate 中。但是,如果您确实需要临时解决方法,则可以像这样创建一个过滤器并检索 keyWindow。
let window = UIApplication.shared.windows.filter $0.isKeyWindow.first
【讨论】:
这应该是对matt's answer的评论或编辑,而不是单独的答案【参考方案9】:如果你想在任何 ViewController 中使用它,那么你可以简单地使用。
self.view.window
【讨论】:
【参考方案10】:试试看:
UIApplication.shared.windows.filter $0.isKeyWindow .first?.rootViewController!.present(alert, animated: true, completion: nil)
【讨论】:
这应该是对matt's answer的评论或编辑,而不是单独的答案【参考方案11】:也适用于 Objective-C 解决方案
@implementation UIWindow (iOS13)
+ (UIWindow*) keyWindow
NSPredicate *isKeyWindow = [NSPredicate predicateWithFormat:@"isKeyWindow == YES"];
return [[[UIApplication sharedApplication] windows] filteredArrayUsingPredicate:isKeyWindow].firstObject;
@end
【讨论】:
【参考方案12】:正如许多开发人员要求使用 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;
【讨论】:
【参考方案13】:灵感来自berni的回答
let keyWindow = Array(UIApplication.shared.connectedScenes)
.compactMap $0 as? UIWindowScene
.flatMap $0.windows
.first(where: $0.isKeyWindow )
【讨论】:
我以一种非常奇怪的方式检查了这段代码,它比其他代码运行得更好。在其他人崩溃的地方,无论前景/背景如何,它都能正常工作...... 既然connectedScenes是一个集合,这里需要转换成数组吗?【参考方案14】: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;
【讨论】:
【参考方案15】: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
【讨论】:
当应用程序从后台返回时无法工作的部分只是在生产中咬了我一口。在调试器中,它总是返回一个窗口,但是当手动运行时,当应用程序从后台启动时,它会定期失败【参考方案16】:- (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;
【讨论】:
【参考方案17】:如果您的应用尚未更新为采用基于场景的应用生命周期,另一种获取活动窗口对象的简单方法是通过UIApplicationDelegate
:
let window = UIApplication.shared.delegate?.window
let rootViewController = window??.rootViewController
【讨论】:
【参考方案18】:我有answered the question on a duplicate feed,由于我在这里找不到提供尽可能多代码的答案(已评论),这是我的贡献:
(在 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
:
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
你可以把它放在任何你想要的地方,但我个人将它作为extension
添加到UIViewController
。
这让我可以添加更多有用的扩展,例如更容易呈现UIViewController
s 的扩展:
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)
【讨论】:
【参考方案19】:我遇到了同样的问题。
我为视图分配了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;
希望对你有帮助。
【讨论】:
【参考方案20】:当.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
【讨论】:
【参考方案21】:如果您使用 SwiftLint 和 'first_where' 规则并希望消除交战:
UIApplication.shared.windows.first(where: $0.isKeyWindow )
【讨论】:
以上是关于如何解决:“keyWindow”在 iOS 13.0 中已弃用的主要内容,如果未能解决你的问题,请参考以下文章
UIAlertViews、UIActionSheets 和 keyWindow 问题
keywindow 在 ([[[UIApplication sharedApplication] keyWindow] addSubview:myView];) iOS 6 中不起作用
在 iOS8 中: UIPopoverController presentPopoverFromRect 不再适用于 keyWindow