43

我正在升级我的应用程序以使用UISceneiOS 13 中定义的新模式,但是该应用程序的一个关键部分已停止工作。我一直在使用 aUIWindow来覆盖屏幕上的当前内容并向用户呈现新信息,但在我正在使用的当前测试版(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 beta 中做到这一点?

谢谢

编辑

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

4

12 回答 12

35

我在升级 iOS 13 场景模式的代码时遇到了同样的问题。使用您的第二个代码片段的一部分,我设法修复了所有问题,因此我的窗口再次出现。除了最后一行,我和你做的一样。尝试删除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(...),因为您使用该控制器显示一个窗口并立即呈现一些“自我”,所以这取决于“自我”到底是什么。

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

希望有帮助,@SirOz

于 2019-07-23T15:17:18.603 回答
13

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

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)
于 2019-09-30T12:16:20.487 回答
7

以下是在 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()
}
于 2020-01-29T13:02:36.233 回答
5

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

于 2019-10-04T08:35:59.677 回答
4

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

我以这种方式解决了我的问题:

  • 我添加了一个变量
var windowsPopUp: UIWindow?
  • 我修改了代码以显示弹出窗口:
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 将被关闭,但窗口仍然处于活动状态,不允许使用应用程序进行迭代(使用应用程序窗口)

于 2019-07-31T14:35:17.357 回答
3

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

这是一个小的 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)
    }
}

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

private let cheatSheet = DebugCheatSheet()

override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
    if motion == .motionShake {
        cheatSheet.present()
    }
}
于 2019-10-11T07:49:55.557 回答
2

你可以这样尝试:

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

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

于 2020-08-23T04:54:29.023 回答
1

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

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

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

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

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

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

}

于 2019-09-29T17:52:40.863 回答
1

这是一种在呈现的视图控制器被解除和解除分配后持有对 createdUIWindow和释放它的强引用的一种有点 hacky 的方法。只要确保你不做参考循环。

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)
于 2019-11-20T01:40:24.433 回答
1

需要有指针为 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)
于 2020-01-16T09:31:11.280 回答
0

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)
        }
    }
}
于 2020-02-15T13:00:53.540 回答
0

除了关于创建对 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
  }

}
于 2020-08-25T00:29:29.863 回答