【问题标题】:UIWindow not showing over content in iOS 13UIWindow 未在 iOS 13 中显示内容
【发布时间】:2019-11-25 09:18:18
【问题描述】:

我正在升级我的应用程序以使用 iOS 13 中定义的新 UIScene 模式,但是该应用程序的一个关键部分已停止工作。 我一直在使用UIWindow 覆盖屏幕上的当前内容并向用户呈现新信息,但在我正在使用的当前测试版(iOS + XCode beta 3)中,窗口会出现,但随后会立即消失.

这是我使用的代码,现在不起作用:

let window = UIWindow(frame: UIScreen.main.bounds)
let viewController = UIViewController()
viewController.view.backgroundColor = .clear
window.rootViewController = viewController
window.windowLevel = UIWindow.Level.statusBar + 1
window.makeKeyAndVisible()
viewController.present(self, animated: true, completion: nil)

我尝试了很多东西,包括使用WindowScenes 来展示新的UIWindow,但找不到任何实际的文档或示例。

我的一个尝试(没有奏效 - 窗口出现并立即消失的相同行为)

let windowScene = UIApplication.shared.connectedScenes.first
if let windowScene = windowScene as? UIWindowScene {
    let window = UIWindow(windowScene: windowScene)
    let viewController = UIViewController()
    viewController.view.backgroundColor = .clear
    window.rootViewController = viewController
    window.windowLevel = UIWindow.Level.statusBar + 1
    window.makeKeyAndVisible()
    viewController.present(self, animated: true, completion: nil)
}

在 iOS 13 测试版中是否有人能够做到这一点?

谢谢

编辑

从提出这个问题到发布 iOS 13 的最终版本已经过去了一段时间。下面有很多答案,但几乎所有答案都包含一件事 - 添加对 UIWindow 的强/强引用。您可能需要包含一些与新场景相关的代码,但请先尝试添加强引用。

【问题讨论】:

  • 您需要保留对窗口对象的引用
  • @LeoDabus 我想在当前窗口上显示一个新窗口吗?我将在哪里使用对我的窗口对象的引用?顶部代码块在 13 beta 之前完美运行
  • 将窗口声明移出闭包
  • 尝试var window: UIWindow? if let windowScene = windowScene as? UIWindowScene { window = .init(windowScene: windowScene) 然后使用可选链接window?.whatever
  • @LeoDabus 啊我明白了。我试过了,但没有用。 WindowwindowScene 都是已成功引用的实际对象,问题不在于找不到这些对象,我只是得到与顶部代码块和底部代码块相同的行为。谢谢

标签: ios swift uiwindow ios13 uiscenedelegate


【解决方案1】:

我在升级 iOS 13 场景模式的代码时遇到了同样的问题。使用您的第二个代码 sn-p 的一部分,我设法修复了所有问题,因此我的窗口再次出现。除了最后一行,我和你做的一样。尝试删除viewController.present(...)。这是我的代码:

let windowScene = UIApplication.shared
                .connectedScenes
                .filter { $0.activationState == .foregroundActive }
                .first
if let windowScene = windowScene as? UIWindowScene {
    popupWindow = UIWindow(windowScene: windowScene)
}

然后我像你一样呈现它:

popupWindow?.frame = UIScreen.main.bounds
popupWindow?.backgroundColor = .clear
popupWindow?.windowLevel = UIWindow.Level.statusBar + 1
popupWindow?.rootViewController = self as? UIViewController
popupWindow?.makeKeyAndVisible()

无论如何,我个人认为问题出在viewController.present(...),因为您显示一个带有该控制器的窗口并立即呈现一些“自我”,所以这取决于“自我”到底是什么。

另外值得一提的是,我存储了对您从控制器内部移动的窗口的引用。如果这对您仍然没用,我只能显示使用此代码的我的小 repo。查看AnyPopupController.swiftPopup.swift 文件的内部。

希望对您有所帮助,@SirOz

【讨论】:

  • 不覆盖状态栏
  • 只是说出来,因为我花了几次才注意到它:与我的应用程序相关的主要区别是弹出窗口应该使用适当的窗口场景进行初始化:UIWindow(windowScene: windowScene)。我的应用程序中的其他所有内容都保持不变。干得好!
【解决方案2】:

基于所有建议的解决方案,我可以提供我自己的代码版本:

private var window: UIWindow!

extension UIAlertController {
    func present(animated: Bool, completion: (() -> Void)?) {
        window = UIWindow(frame: UIScreen.main.bounds)
        window.rootViewController = UIViewController()
        window.windowLevel = .alert + 1
        window.makeKeyAndVisible()
        window.rootViewController?.present(self, animated: animated, completion: completion)
    }

    open override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        window = nil
    }
}

使用方法:

// Show message (from any place)
let alert = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Button", style: .cancel))
alert.present(animated: true, completion: nil)

【讨论】:

  • @mHopkins 在 iOS 13 上测试!
  • 是的,正如@andrey-m 所注意到的,您只需要存储您想要呈现的 UIWindow 的强引用。
  • 你没有拖拽dismiss时没有调用viewDidDisappear的问题吗?
  • 我一直在寻找它的 Objective C 版本,但我在互联网上的任何地方都找不到它。由于我是 IOS 开发新手,因此我很难将其快速转换为目标 c。
【解决方案3】:

以下是在 iOS 13 的新窗口中显示视图控制器的步骤:

  1. 检测焦点UIWindowScene
extension UIWindowScene {
    static var focused: UIWindowScene? {
        return UIApplication.shared.connectedScenes
            .first { $0.activationState == .foregroundActive && $0 is UIWindowScene } as? UIWindowScene
    }
}
  1. 为焦点场景创建UIWindow
if let window = UIWindowScene.focused.map(UIWindow.init(windowScene:)) {
  // ...
}
  1. 在该窗口中显示UIViewController
let myViewController = UIViewController()

if let window = UIWindowScene.focused.map(UIWindow.init(windowScene:)) {
    window.rootViewController = myViewController
    window.makeKeyAndVisible()
}

【讨论】:

    【解决方案4】:

    您只需要存储您想要呈现的UIWindowstrong 参考。似乎显示的引擎盖视图控制器没有引用窗口。

    【讨论】:

    • 你可以在@Vergiliy answer中看到的实现
    【解决方案5】:

    谢谢@glassomoss。我的问题是 UIAlertController。

    我这样解决了我的问题:

    • 我添加了一个变量
    var windowsPopUp: UIWindow?
    
    • 我修改了代码以显示 PopUp:
    public extension UIAlertController {
        func showPopUp() {
            windowsPopUp = UIWindow(frame: UIScreen.main.bounds)
            let vc = UIViewController()
            vc.view.backgroundColor = .clear
            windowsPopUp!.rootViewController = vc
            windowsPopUp!.windowLevel = UIWindow.Level.alert + 1
            windowsPopUp!.makeKeyAndVisible()
            vc.present(self, animated: true)
        }
    }
    
    • 在我添加的 UIAlertController 的操作中:
    windowsPopUp = nil
    

    如果没有最后一行,PopUp 将被关闭,但窗口仍处于活动状态,不允许与应用程序(与应用程序窗口)进行迭代

    【讨论】:

    • 另一个有趣的 iOS 13 错误,如果你像这样添加第二个 UIWindow,所有的触摸都会被吃掉,即使 userInteractionEnabled = NO...
    • @jjxtra 一种解决方法是将隐藏设置为是。不理想但有效。
    • 解决方案是设置一个透明视图和禁用用户交互的空视图控制器作为新UIWindow的根视图控制器。如果没有这个,一切都会中断,但仅限于 iOS 13。在 iOS 12 及更早版本上,您可以在两个窗口上设置相同的根视图控制器。
    【解决方案6】:

    正如其他人所提到的,问题是需要对窗口的强引用。因此,为了确保在使用后再次删除此窗口,我将所需的所有内容都封装在它自己的类中..

    这是一个小小的 Swift 5 sn-p:

    class DebugCheatSheet {
    
        private var window: UIWindow?
    
        func present() {
            let vc = UIViewController()
            vc.view.backgroundColor = .clear
    
            window = UIWindow(frame: UIScreen.main.bounds)
            window?.rootViewController = vc
            window?.windowLevel = UIWindow.Level.alert + 1
            window?.makeKeyAndVisible()
    
            vc.present(sheet(), animated: true, completion: nil)
        }
    
        private func sheet() -> UIAlertController {
            let alert = UIAlertController.init(title: "Cheatsheet", message: nil, preferredStyle: .actionSheet)
            addAction(title: "Ok", style: .default, to: alert) {
                print("Alright...")
            }
            addAction(title: "Cancel", style: .cancel, to: alert) {
                print("Cancel")
            }
            return alert
        }
    
        private func addAction(title: String?, style: UIAlertAction.Style, to alert: UIAlertController, action: @escaping () -> ()) {
            let action = UIAlertAction.init(title: title, style: style) { [weak self] _ in
                action()
                alert.dismiss(animated: true, completion: nil)
                self?.window = nil
            }
            alert.addAction(action)
        }
    }
    

    这是我使用它的方式。它来自整个应用程序视图层次结构中的最低视图控制器,但也可以在其他任何地方使用:

    private let cheatSheet = DebugCheatSheet()
    
    override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
        if motion == .motionShake {
            cheatSheet.present()
        }
    }
    

    【讨论】:

      【解决方案7】:

      你可以这样尝试:

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

      用法:

      if let rootVC = UIWindow.key?.rootViewController {
          rootVC.present(nextViewController, animated: true, completion: nil)
      }
      

      继续编码...... :)

      【讨论】:

      • 更好的解决方案。代码保持干净:)
      【解决方案8】:

      iOS 13 破坏了我用于管理警报的辅助函数。

      因为在某些情况下,您可能需要同时显示多个警报(最新的在旧警报之上),例如,如果您显示的是或否警报,同时您的网络服务返回一个您通过警报显示的错误(这是一个极限情况,但它可能发生),

      我的解决方案是像这样扩展 UIAlertController,让它有自己的 alertWindow 来显示。

      优点是,当您关闭警报时,窗口会自动关闭,因为还有任何强引用,因此无需实施进一步的模组。

      免责声明:我只是实现了它,所以我仍然需要看看它是否一致......

      class AltoAlertController: UIAlertController {
      
      var alertWindow : UIWindow!
      
      func show(animated: Bool, completion: (()->(Void))?)
      {
          alertWindow = UIWindow(frame: UIScreen.main.bounds)
          alertWindow.rootViewController = UIViewController()
          alertWindow.windowLevel = UIWindow.Level.alert + 1
          alertWindow.makeKeyAndVisible()
          alertWindow.rootViewController?.present(self, animated: animated, completion: completion)
      }
      

      }

      【讨论】:

        【解决方案9】:

        这里有一个有点老套的方法来保持对创建的UIWindow 的强引用并在呈现的视图控制器被解除和释放后释放它。 只要确保你不做参考循环。

        private final class WindowHoldingViewController: UIViewController {
        
            private var window: UIWindow?
        
            convenience init(window: UIWindow) {
                self.init()
        
                self.window = window
            }
        
            override func viewDidLoad() {
                super.viewDidLoad()
        
                view.backgroundColor = UIColor.clear
            }
        
            override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
                let view = DeallocatingView()
                view.onDeinit = { [weak self] in
                    self?.window = nil
                }
                viewControllerToPresent.view.addSubview(view)
        
                super.present(viewControllerToPresent, animated: flag, completion: completion)
            }
        
            private final class DeallocatingView: UIView {
        
                var onDeinit: (() -> Void)?
        
                deinit {
                    onDeinit?()
                }
            }
        }
        

        用法:

        let vcToPresent: UIViewController = ...
        let window = UIWindow() // or create via window scene
        ...
        window.rootViewController = WindowHoldingViewController(window: window)
        ...
        window.rootViewController?.present(vcToPresent, animated: animated, completion: completion)
        

        【讨论】:

          【解决方案10】:

          需要有指针为 ios13 创建窗口。

          我的代码示例:

           extension UIAlertController {
          
              private static var _aletrWindow: UIWindow?
              private static var aletrWindow: UIWindow {
                  if let window = _aletrWindow {
                      return window
                  } else {
                      let window = UIWindow(frame: UIScreen.main.bounds)
                      window.rootViewController = UIViewController()
                      window.windowLevel = UIWindowLevelAlert + 1
                      window.backgroundColor = .clear
                      _aletrWindow = window
                      return window
                  }
              }
          
              func presentGlobally(animated: Bool, completion: (() -> Void)? = nil) {
                  UIAlertController.aletrWindow.makeKeyAndVisible()
                  UIAlertController.aletrWindow.rootViewController?.present(self, animated: animated, completion: completion)
              }
          
              open override func viewDidDisappear(_ animated: Bool) {
                  super.viewDidDisappear(animated)
                  UIAlertController.aletrWindow.isHidden = true
              }
          
          }
          

          使用:

          let alert = UIAlertController(...
          ...
          
          alert.presentGlobally(animated: true)
          

          【讨论】:

            【解决方案11】:

            Swift 4.2 iOS 13 UIAlertController 扩展

            此代码完全适用于 iOS 11、12 和 13

            import Foundation
            import UIKit
            
            extension UIAlertController{
                private struct AssociatedKeys {
                    static var alertWindow = "alertWindow"
                }
                var alertWindow:UIWindow?{
                    get{
                        guard let alertWindow = objc_getAssociatedObject(self, &AssociatedKeys.alertWindow) as? UIWindow else {
                            return nil
                        }
                        return alertWindow
                    }
                    set(value){
                        objc_setAssociatedObject(self,&AssociatedKeys.alertWindow,value,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                    }
                }
            
                func show(animated:Bool) {
                    self.alertWindow = UIWindow(frame: UIScreen.main.bounds)
                    self.alertWindow?.rootViewController = UIViewController()
                    self.alertWindow?.windowLevel = UIWindow.Level.alert + 1
                    if #available(iOS 13, *){
                        let mySceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate
                        mySceneDelegate!.window?.rootViewController?.present(self, animated: animated, completion: nil)
                    }
                    else{
                        self.alertWindow?.makeKeyAndVisible()
                        self.alertWindow?.rootViewController?.present(self, animated: animated, completion: nil)
                    }
                }
            }
            

            【讨论】:

              【解决方案12】:

              除了关于创建对 UIWindow 的引用然后以模态方式呈现的答案之外,我还包含了一段关于如何解除它的代码。

              class PresentingViewController: UIViewController {
                  private var coveringWindow: UIWindow?
              
                func presentMovie() {
                  let playerVC = MoviePlayerViewController()
                  playerVC.delegate = self
                  playerVC.modalPresentationStyle = .overFullScreen
                  playerVC.modalTransitionStyle = .coverVertical
              
                  self.coverPortraitWindow(playerVC)
                }
              
                func coverPortraitWindow(_ movieController: MoviePlayerViewController) {
              
                  let windowScene = UIApplication.shared
                      .connectedScenes
                      .filter { $0.activationState == .foregroundActive }
                      .first
                  if let windowScene = windowScene as? UIWindowScene {
                      self.coveringWindow = UIWindow(windowScene: windowScene)
              
                      let rootController = UIViewController()
                      rootController.view.backgroundColor = .clear
              
                      self.coveringWindow!.windowLevel = .alert + 1
                      self.coveringWindow!.isHidden = false
                      self.coveringWindow!.rootViewController = rootController
                      self.coveringWindow!.makeKeyAndVisible()
              
                      rootController.present(movieController, animated: true)
                  }
                }
              
                func uncoverPortraitWindow() {
                  guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
                      let sceneDelegate = windowScene.delegate as? SceneDelegate
                      else {
                          return
                  }
                  sceneDelegate.window?.makeKeyAndVisible()
                  self.coveringWindow = nil
                }
              
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-09-15
                • 2020-01-19
                • 1970-01-01
                • 2022-01-13
                • 1970-01-01
                相关资源
                最近更新 更多