web-dev-qa-db-fra.com

UIWindow n'affiche pas le contenu dans iOS 13

Je mets à jour mon application pour utiliser les nouveaux modèles UIScene définis dans iOS 13, mais une partie critique de l'application a cessé de fonctionner. J'utilise un UIWindow pour couvrir le contenu actuel à l'écran et présenter de nouvelles informations à l'utilisateur, mais dans la version bêta actuelle avec laquelle je travaille (iOS + XCode beta 3), la fenêtre apparaîtra, mais ensuite disparaissent tout de suite.

Voici le code que j'utilisais, qui ne fonctionne plus:

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)

J'ai essayé beaucoup de choses, y compris en utilisant WindowScenes pour présenter le nouveau UIWindow, mais je ne trouve pas de documentation ou d'exemples réels.

Une de mes tentatives (n'a pas fonctionné - même comportement avec apparition et fermeture immédiate de la fenêtre)

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

Quelqu'un a-t-il déjà pu le faire dans iOS 13 beta?

Merci

MODIFIER

Un certain temps s'est écoulé entre la demande et la sortie de la version finale d'iOS 13. Il y a beaucoup de réponses ci-dessous, mais presque toutes incluent une chose - Ajout d'une référence forte/plus forte à la fenêtre UIWindow. Vous devrez peut-être inclure du code concernant les nouvelles scènes, mais essayez d'abord d'ajouter la référence forte.

27
mHopkins

Je rencontrais les mêmes problèmes lors de la mise à niveau de mon code pour le modèle de scènes iOS 13. Avec des parties de votre deuxième extrait de code, j'ai réussi à tout réparer pour que mes fenêtres réapparaissent. Je faisais la même chose que toi sauf pour la dernière ligne. Essayez de supprimer viewController.present(...). Voici mon code:

let windowScene = UIApplication.shared
                .connectedScenes
                .filter { $0.activationState == .foregroundActive }
                .first
if let windowScene = windowScene as? UIWindowScene {
    popupWindow = UIWindow(windowScene: windowScene)
}

Ensuite, je le présente comme vous le faites:

popupWindow?.frame = UIScreen.main.bounds
popupWindow?.backgroundColor = .clear
popupWindow?.windowLevel = UIWindow.Level.statusBar + 1
popupWindow?.rootViewController = self as? UIViewController
popupWindow?.makeKeyAndVisible()

Quoi qu'il en soit, je pense personnellement que le problème est dans viewController.present(...), parce que vous affichez une fenêtre avec ce contrôleur et présentez immédiatement un "moi", donc cela dépend de ce qu'est vraiment "moi".

Il convient également de mentionner que je stocke une référence à la fenêtre à partir de laquelle vous vous déplacez à l'intérieur de mon contrôleur. Si cela vous est encore inutile, je ne peux que montrer mon petit repo qui utilise ce code. Jetez un œil aux fichiers AnyPopupController.Swift Et Popup.Swift.

J'espère que ça aide, @SirOz

21
glassomoss

Sur la base de toutes les solutions proposées, je peux proposer ma propre version du code:

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

Comment utiliser:

// 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)
11
Vergiliy

Il vous suffit de stocker la référence strong de UIWindow que vous souhaitez présenter. Il semble que sous le capot, le contrôleur de vue présenté ne fait pas référence à la fenêtre.

3
Andrey M.

Merci @glassomoss. Mon problème est avec UIAlertController.

J'ai résolu mon problème de cette façon:

  • J'ai ajouté une variable
var windowsPopUp: UIWindow?
  • J'ai modifié le code pour afficher le PopUp:
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)
    }
}
  • Dans l'action de l'UIAlertController, j'ai ajouté:
windowsPopUp = nil

sans la dernière ligne le PopUp est fermé mais les fenêtres restent actives ne permettant pas l'itération avec l'application (avec la fenêtre de l'application)

3
SirOz

Comme tout le monde l'a mentionné, le problème est qu'une référence forte à la fenêtre est nécessaire. Donc, pour m'assurer que cette fenêtre est à nouveau supprimée après utilisation, j'ai encapsulé tout le nécessaire dans sa propre classe.

Voici un petit Swift 5 extrait:

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

Et voici comment je l'utilise .. Il s'agit du contrôleur de vue le plus bas de la hiérarchie de vue des applications, mais il peut également être utilisé partout ailleurs:

private let cheatSheet = DebugCheatSheet()

override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
    if motion == .motionShake {
        cheatSheet.present()
    }
}
2
Wiingaard

iOS 13 a cassé mes fonctions d'assistance pour la gestion des alertes.

Parce qu'il peut y avoir des cas où vous avez besoin que plusieurs alertes soient affichées en même temps (la plus récente au-dessus de l'ancienne), par exemple au cas où vous afficheriez une alerte oui ou non et en attendant votre service web revient avec une erreur que vous affichez via une alerte (c'est un cas limite mais ça peut arriver),

ma solution est d'étendre le UIAlertController comme ceci et de lui laisser sa propre alerteWindow à partir de laquelle.

Le pro est que lorsque vous fermez l'alerte, la fenêtre est automatiquement fermée car il n'y a plus de référence forte, donc aucun autre mod à implémenter.

Avertissement: je viens de l'implémenter, j'ai donc encore besoin de voir si c'est cohérent ...

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

}

1
Il Pisanello

Voici les étapes pour présenter un contrôleur de vue dans une nouvelle fenêtre sur iOS 13:

  1. Détecte le UIWindowScene focalisé.
extension UIWindowScene {
    static var focused: UIWindowScene? {
        return UIApplication.shared.connectedScenes
            .first { $0.activationState == .foregroundActive && $0 is UIWindowScene } as? UIWindowScene
    }
}
  1. Créez UIWindow pour la scène focalisée.
if let window = UIWindowScene.focused.map(UIWindow.init(windowScene:)) {
  // ...
}
  1. Présentez UIViewController dans cette fenêtre.
let myViewController = UIViewController()

if let window = UIWindowScene.focused.map(UIWindow.init(windowScene:)) {
    window.rootViewController = myViewController
    window.makeKeyAndVisible()
}
1
Vadim Bulavin

Besoin de pointer une fenêtre créée pour ios13.

exemple mon code:

 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
    }

}

utilisation:

let alert = UIAlertController(...
...

alert.presentGlobally(animated: true)
0
RomanV

Voici un moyen un peu hacky de conserver une référence forte à UIWindow créé et de le libérer après que le contrôleur de vue présenté soit rejeté et désalloué. Assurez-vous simplement de ne pas faire de cycles de référence.

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

Usage:

let vcToPresent: UIViewController = ...
let window = UIWindow() // or create via window scene
...
window.rootViewController = WindowHoldingViewController(window: window)
...
window.rootViewController?.present(vcToPresent, animated: animated, completion: completion)
0
younke