【问题标题】:How to resolve: 'keyWindow' was deprecated in iOS 13.0如何解决:“keyWindow”在 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 的“共享”的场景?)

标签: swift ios13 uiwindow uiscene


【解决方案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
  • 这里也可以测试activationStateforegroundInactive,在我的测试中,如果出现警报就会出现这种情况。
  • 此代码为我生成 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
}

【讨论】:

  • 这是最优雅的答案,展示了 Swift extensions 的美丽。 ?
  • 可用性检查几乎没有必要,因为 windowsisKeyWindow 自 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:

关键窗口

  • 手动跟踪窗口

【讨论】:

    【解决方案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

                  这让我可以添加更多有用的扩展,例如更容易呈现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)
                          }
                      }
                      
                  }
                  

                  【讨论】:

                    【解决方案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 })
                        

                        【讨论】:

                          猜你喜欢
                          • 2020-03-04
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2020-01-12
                          • 2023-01-16
                          • 2020-09-07
                          相关资源
                          最近更新 更多