Я обновляю свое приложение, чтобы использовать новые шаблоны 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?
Спасибо
РЕДАКТИРОВАТЬ
Между вопросом об этом и выпуском финальной версии iOS 13 прошло некоторое время. Ниже много ответов, но почти все они включают одно — Добавление сильной/сильной ссылки на UIWindow. Возможно, вам потребуется включить некоторый код, относящийся к новым сценам, но сначала попробуйте добавить сильную ссылку.
@LeoDabus Я пытаюсь представить новое окно поверх текущего? Где бы я использовал ссылку на мой объект окна? Верхний блок кода работал отлично до 13 бета
Переместите объявление окна из закрытия
попробуйте var window: UIWindow?if let windowScene = windowScene as? UIWindowScene {window = .init(windowScene: windowScene), затем используйте дополнительную цепочку window?.whatever
@LeoDabus Ааа, я понимаю. Я пробовал это, но это не сработало. Window и windowScene являются фактическими объектами, на которые успешно ссылаются, проблема не в том, что он не может найти эти объекты, я просто получаю то же поведение, что и верхний блок кода с нижним блоком кода. Спасибо
@LeoDabus Спасибо за ваше предложение сохранить ссылку на окно. Это решило ту же проблему для меня!





У меня возникли те же проблемы при обновлении моего кода для шаблона сцен 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
он не перекрывает строку состояния
Просто чтобы отметить это, потому что мне потребовалось несколько проходов, чтобы заметить это: основное отличие, относящееся к моему приложению, заключается в том, что всплывающее окно должно быть инициализировано соответствующей сценой окна: UIWindow(windowScene: windowScene). Все остальное в моем приложении осталось прежним. Прекрасная работа!
Спасибо @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
без последней строки всплывающее окно закрывается, но окна остаются активными, не позволяя итерации с приложением (с окном приложения)
Еще одна забавная ошибка iOS 13: если вы добавите второй подобный UIWindow, все касания будут съедены, даже если userInteractionEnabled = NO...
@jjxtra обходной путь - установить скрытое значение ДА. Не идеально, но работает.
Решение состоит в том, чтобы установить пустой контроллер представления с прозрачным представлением и отключенным взаимодействием с пользователем в качестве корневого контроллера представления нового UIWindow. Без этого все ломается, но только на iOS 13. На iOS 12 и более ранних можно было установить один и тот же контроллер корневого представления на оба окна.
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)
}
}
На основании всех предложенных решений могу предложить свой вариант кода:
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, которую вы хотите представить.
У вас нет проблемы с тем, что viewDidDisappear не вызывается при отклонении перетаскивания?
Я искал версию для Objective C, но нигде не могу найти ее в Интернете. И я борюсь с быстрым, чтобы преобразовать его в цель c, поскольку я новичок в разработке IOS.
Вам просто нужно сохранить ссылку сильныйсильный> на UIWindow, которую вы хотите представить. Кажется, что под капотом представленный контроллер представления не ссылается на окно.
Реализация, которую вы можете увидеть в ответе @Vergily
Как уже упоминалось, проблема в том, что требуется сильная ссылка на окно. Поэтому, чтобы убедиться, что это окно снова удаляется после использования, я инкапсулировал все необходимое в его собственном классе.
Вот небольшой фрагмент 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()
}
}
Вот немного хакерский способ сохранить сильную ссылку на созданный 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)
Нужно иметь указатель созданного окна для 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)
Вот шаги, чтобы представить контроллер представления в новом окне на 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()
}
Расширение 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)
}
}
}
Вы можете попробовать так:
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 и последующем ее модальном представлении я включил в свой код раздел о том, как я его отклоняю.
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
}
}
вам нужно сохранить ссылку на ваш объект окна