[英]UIWindow not showing over content in iOS 13
我正在升級我的應用程序以使用 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 的強/強引用。 您可能需要包含一些與新場景相關的代碼,但請先嘗試添加強引用。
我在升級 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.swift
和Popup.swift
文件。
希望有幫助,@SirOz
基於所有建議的解決方案,我可以提供我自己的代碼版本:
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)
以下是在 iOS 13 上的新窗口中顯示視圖控制器的步驟:
UIWindowScene
。extension UIWindowScene {
static var focused: UIWindowScene? {
return UIApplication.shared.connectedScenes
.first { $0.activationState == .foregroundActive && $0 is UIWindowScene } as? UIWindowScene
}
}
UIWindow
。if let window = UIWindowScene.focused.map(UIWindow.init(windowScene:)) {
// ...
}
UIViewController
。let myViewController = UIViewController()
if let window = UIWindowScene.focused.map(UIWindow.init(windowScene:)) {
window.rootViewController = myViewController
window.makeKeyAndVisible()
}
謝謝@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)
}
}
windowsPopUp = nil
如果沒有最后一行,則彈出窗口將被關閉,但窗口仍處於活動狀態,不允許與應用程序(使用應用程序窗口)進行迭代
您只需要存儲要呈現的UIWindow
強引用。 似乎在呈現的引擎蓋視圖控制器下沒有引用窗口。
正如其他人所提到的,問題是需要對窗口進行強引用。 所以為了確保這個窗口在使用后再次被移除,我把所有需要的東西都封裝在了它自己的類中..
這是一個小小的 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()
}
}
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)
}
}
這是持有對創建的UIWindow
的強引用並在呈現的視圖控制器被解除和解除分配后釋放它的一種有點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)
需要有一個為 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)
你可以這樣試試:
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)
}
繼續編碼........ :)
在提供自定義警報的函數外部為UIWindow
創建變量可以解決此問題,是的,這很奇怪。
而且您必須將該變量設置回nil
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)
}
}
}
除了關於創建對 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.