UIWindow не отображается поверх контента в iOS 13
Я обновляю свое приложение, чтобы использовать новый UIScene
шаблоны, как определено в iOS 13, однако критическая часть приложения перестала работать. Я использовал 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?
Спасибо
2 ответа
У меня были те же проблемы при обновлении кода для шаблона 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()
}
Вам просто нужно сохранить сильную ссылку наUIWindow
что вы хотите представить. Кажется, что представленный под капотом контроллер представления не ссылается на окно.
Спасибо @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 отклоняется, но окна остаются активными, не позволяя выполнять итерации с приложением (с окном приложения)
Как уже упоминалось, проблема в том, что требуется четкая ссылка на окно. Поэтому, чтобы убедиться, что это окно снова удалено после использования, я инкапсулировал все необходимое в его собственный класс.
Вот небольшой фрагмент 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()
}
}
Вы можете попробовать вот так:
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)
}
Продолжайте кодировать........:)
I OS 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)
}
}
Нужен указатель созданного окна для 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)
Вот несколько хитрый способ держать сильную ссылку на созданный 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)
В дополнение к ответам о создании ссылки на 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
}
}
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)
}
}
}