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?

Спасибо

РЕДАКТИРОВАТЬ

Между вопросом об этом и выпуском финальной версии iOS 13 прошло некоторое время. Ниже много ответов, но почти все они включают одно — Добавление сильной/сильной ссылки на UIWindow. Возможно, вам потребуется включить некоторый код, относящийся к новым сценам, но сначала попробуйте добавить сильную ссылку.

вам нужно сохранить ссылку на ваш объект окна

Leo Dabus 16.07.2019 17:28

@LeoDabus Я пытаюсь представить новое окно поверх текущего? Где бы я использовал ссылку на мой объект окна? Верхний блок кода работал отлично до 13 бета

mHopkins 16.07.2019 17:37

Переместите объявление окна из закрытия

Leo Dabus 16.07.2019 17:38

попробуйте var window: UIWindow?if let windowScene = windowScene as? UIWindowScene {window = .init(windowScene: windowScene), затем используйте дополнительную цепочку window?.whatever

Leo Dabus 16.07.2019 17:43

@LeoDabus Ааа, я понимаю. Я пробовал это, но это не сработало. Window и windowScene являются фактическими объектами, на которые успешно ссылаются, проблема не в том, что он не может найти эти объекты, я просто получаю то же поведение, что и верхний блок кода с нижним блоком кода. Спасибо

mHopkins 16.07.2019 17:48

@LeoDabus Спасибо за ваше предложение сохранить ссылку на окно. Это решило ту же проблему для меня!

Brian Boyle 28.08.2019 17:14
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
43
6
33 664
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

Ответ принят как подходящий

У меня возникли те же проблемы при обновлении моего кода для шаблона сцен 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

он не перекрывает строку состояния

Neil Galiaskarov 08.10.2019 09:24

Просто чтобы отметить это, потому что мне потребовалось несколько проходов, чтобы заметить это: основное отличие, относящееся к моему приложению, заключается в том, что всплывающее окно должно быть инициализировано соответствующей сценой окна: UIWindow(windowScene: windowScene). Все остальное в моем приложении осталось прежним. Прекрасная работа!

Wayne 07.02.2020 23:59

Спасибо @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

без последней строки всплывающее окно закрывается, но окна остаются активными, не позволяя итерации с приложением (с окном приложения)

Еще одна забавная ошибка iOS 13: если вы добавите второй подобный UIWindow, все касания будут съедены, даже если userInteractionEnabled = NO...

jjxtra 19.09.2019 23:01

@jjxtra обходной путь - установить скрытое значение ДА. Не идеально, но работает.

RunLoop 22.09.2019 06:36

Решение состоит в том, чтобы установить пустой контроллер представления с прозрачным представлением и отключенным взаимодействием с пользователем в качестве корневого контроллера представления нового UIWindow. Без этого все ломается, но только на iOS 13. На iOS 12 и более ранних можно было установить один и тот же контроллер корневого представления на оба окна.

jjxtra 22.09.2019 17:46

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!

Vergiliy 30.09.2019 16:16

Да, как заметил @andrey-m, вам просто нужно сохранить сильную ссылку на UIWindow, которую вы хотите представить.

Ilja Popov 22.10.2019 14:23

У вас нет проблемы с тем, что viewDidDisappear не вызывается при отклонении перетаскивания?

RealMan 20.04.2020 16:13

Я искал версию для Objective C, но нигде не могу найти ее в Интернете. И я борюсь с быстрым, чтобы преобразовать его в цель c, поскольку я новичок в разработке IOS.

Renascent 17.12.2021 11:33

Вам просто нужно сохранить ссылку сильныйсильный> на UIWindow, которую вы хотите представить. Кажется, что под капотом представленный контроллер представления не ссылается на окно.

Реализация, которую вы можете увидеть в ответе @Vergily

Ilja Popov 22.10.2019 14:24

Как уже упоминалось, проблема в том, что требуется сильная ссылка на окно. Поэтому, чтобы убедиться, что это окно снова удаляется после использования, я инкапсулировал все необходимое в его собственном классе.

Вот небольшой фрагмент 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:

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

Расширение 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)
}

Продолжайте кодировать........ :)

Гораздо лучшее решение. Код остается чистым :)

ibyte 31.10.2021 09:42

В дополнение к ответам о создании ссылки на 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
  }

}

Другие вопросы по теме