简体   繁体   English

UIWindow 未显示在 iOS 13 中的内容

[英]UIWindow not showing over content in iOS 13

I am upgrading my app to use the new UIScene patterns as defined in iOS 13, however a critical part of the app has stopped working.我正在升级我的应用程序以使用 iOS 13 中定义的新UIScene模式,但是应用程序的一个关键部分已停止工作。 I have been using a UIWindow to cover the current content on the screen and present new information to the user, but in the current beta I am working with (iOS + XCode beta 3) the window will appear, but then disappear straight away.我一直在使用UIWindow来覆盖屏幕上的当前内容并向用户呈现新信息,但在我正在使用的当前测试版(iOS + XCode beta 3)中,窗口将出现,但随后立即消失。

Here is the code I was using, that now does not work:这是我使用的代码,现在不起作用:

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)

I have tried many things, including using WindowScenes to present the new UIWindow , but cannot find any actual documentation or examples out there.我尝试了很多东西,包括使用WindowScenes来呈现新的UIWindow ,但找不到任何实际的文档或示例。

One of my attempts (Did not work - same behaviour with window appearing and dismissing straight away)我的一次尝试(不起作用 - 窗口出现和立即关闭的行为相同)

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

Has anyone been able to do this yet in iOS 13 beta?有没有人能够在 iOS 13 测试版中做到这一点?

Thanks谢谢

EDIT编辑

Some time has passed between asking this and the final version of iOS 13 being released.在提出这个问题和发布 iOS 13 的最终版本之间已经过了一段时间。 There are a lot of answers below, but almost all of them include one thing - Adding a strong/stronger reference to the UIWindow .下面有很多答案,但几乎所有答案都包含一件事 -添加对 UIWindow 的强/强引用 You may need to include some code relating the the new Scenes, but try adding the strong reference first.您可能需要包含一些与新场景相关的代码,但请先尝试添加强引用。

I was experiencing the same problems while upgrading my code for iOS 13 scenes pattern.我在升级 iOS 13 场景模式的代码时遇到了同样的问题。 With parts of your second code snippet I managed to fix everything so my windows are appearing again.使用您的第二个代码片段的一部分,我设法修复了所有内容,因此我的窗口再次出现。 I was doing the same as you except for the last line.除了最后一行,我和你做的一样。 Try removing viewController.present(...) .尝试删除viewController.present(...) Here's my code:这是我的代码:

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

Then I present it like you do:然后我像你一样呈现它:

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

Anyway, I personally think that the problem is in viewController.present(...) , because you show a window with that controller and immediately present some 'self', so it depends on what 'self' really is.无论如何,我个人认为问题出在viewController.present(...) ,因为您显示一个带有该控制器的窗口并立即呈现一些“自我”,因此这取决于“自我”究竟是什么。

Also worth mentioning that I store a reference to the window you're moving from inside my controller.还值得一提的是,我存储了对您从控制器内部移动的窗口的引用。 If this is still useless for you I can only show my small repo that uses this code.如果这对你来说仍然没用,我只能展示我使用这个代码的小仓库 Have a look inside AnyPopupController.swift and Popup.swift files.查看AnyPopupController.swiftPopup.swift文件。

Hope that helps, @SirOz希望有帮助,@SirOz

Based on all the proposed solutions, I can offer my own version of the code:基于所有建议的解决方案,我可以提供我自己的代码版本:

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

How to use:如何使用:

// 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)

Here are the steps to present a view controller in a new window on iOS 13:以下是在 iOS 13 上的新窗口中显示视图控制器的步骤:

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

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

Thank you @glassomoss.谢谢@glassomoss。 My problem is with UIAlertController.我的问题是 UIAlertController。

I solved my problem in this way:我以这种方式解决了我的问题:

  • I added a variable我添加了一个变量
var windowsPopUp: UIWindow?
  • I modified the code to display the 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)
    }
}
  • In the action of the UIAlertController I added:在 UIAlertController 的操作中,我添加了:
windowsPopUp = nil

without the last line the PopUp is dismissed but the windows remains active not allowing the iteration with the application (with the application window)如果没有最后一行,则弹出窗口将被关闭,但窗口仍处于活动状态,不允许与应用程序(使用应用程序窗口)进行迭代

You just need to store strong reference of UIWindow that you want to present.您只需要存储要呈现的UIWindow引用。 It seems that under the hood view controller that presented does not references to the window.似乎在呈现的引擎盖视图控制器下没有引用窗口。

As everyone else mentioned, the issue is that a strong reference to the window is required.正如其他人所提到的,问题是需要对窗口进行强引用。 So to make sure that this window is removed again after use, I encapsulated everything needed in it's own class..所以为了确保这个窗口在使用后再次被移除,我把所有需要的东西都封装在了它自己的类中..

Here's a little Swift 5 snippet:这是一个小小的 Swift 5 片段:

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

And here is how I use it.. It's from the lowest view controller in the whole apps view hierarchy, but could be used from anywhere else also:这是我使用它的方式..它来自整个应用程序视图层次结构中最低的视图控制器,但也可以从其他任何地方使用:

private let cheatSheet = DebugCheatSheet()

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

iOS 13 broke my helper functions for managing alerts. iOS 13 破坏了我管理警报的辅助功能。

Because there may be cases where you need multiple alerts to be displayed at the same time (the most recent above the older) for example in case you're displaying a yes or no alert and in the meanwhile your webservice returns with a error you display via an alert (it's a limit case but it can happen),因为在某些情况下,您可能需要同时显示多个警报(最近的警报高于旧警报),例如,如果您显示是或否警报,同时您的网络服务返回并显示错误通过警报(这是一个极限情况,但它可能发生),

my solution is to extend the UIAlertController like this, and let it have its own alertWindow to be presented from.我的解决方案是像这样扩展 UIAlertController,并让它有自己的 alertWindow 显示出来。

The pro is that when you dismiss the alert the window is automatically dismissed because there is any strong reference left, so no further mods to be implemented.优点是,当您关闭警报时,窗口会自动关闭,因为还剩下任何强引用,因此无需实施进一步的修改。

Disclaimer : I just implemented it, so I still need to see if it's consistent...免责声明:我刚刚实施了它,所以我仍然需要看看它是否一致......

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

} }

Here's a bit hacky way of holding a strong reference to created UIWindow and releasing it after presented view controller is dismissed and deallocated.这是持有对创建的UIWindow的强引用并在呈现的视图控制器被解除和解除分配后释放它的一种有点hacky的方法。 Just make sure you don't make reference cycles.只要确保你不做参考循环。

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?()
        }
    }
}

Usage:用法:

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

Need have pointer a created window for ios13.需要有一个为 ios13 创建的窗口的指针。

example my code:例如我的代码:

 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
    }

}

use:用:

let alert = UIAlertController(...
...

alert.presentGlobally(animated: true)

You can try like this:你可以这样试试:

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

Usage:用法:

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

Keep Coding........ :)继续编码........ :)

Creating a variable for UIWindow outside of function where you present the custom alert can solve this issue and yeah it's weird. 在提供自定义警报的函数外部为UIWindow创建变量可以解决此问题,是的,这很奇怪。

And you have to set that variable back to nil 而且您必须将该变量设置回nil

Swift 4.2 iOS 13 UIAlertController extension Swift 4.2 iOS 13 UIAlertController 扩展

This code full working in iOS 11, 12 and 13此代码完全适用于 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)
        }
    }
}

In addition to the answers about creating a reference to UIWindow and then presenting modally, I've included a section of my code on how I'm dismissing it.除了关于创建对 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
  }

}
fileprivate var windowsPopUp: UIWindow?

public extension UIAlertController {
    func show() {
        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, completion: nil)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        windowsPopUp = nil
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM