Comment résoudre: 'keyWindow' était obsolète dans iOS 13.0

119

J'utilise Core Data avec Cloud Kit et je dois donc vérifier l'état de l'utilisateur iCloud lors du démarrage de l'application. En cas de problème, je souhaite lancer une boîte de dialogue à l'utilisateur, et je le fais en utilisant UIApplication.shared.keyWindow?.rootViewController?.present(...)jusqu'à présent.

Dans Xcode 11 beta 4, il y a maintenant un nouveau message d'obsolescence, me disant:

'keyWindow' était obsolète dans iOS 13.0: ne doit pas être utilisé pour les applications qui prennent en charge plusieurs scènes car il renvoie une fenêtre clé sur toutes les scènes connectées

Comment présenter le dialogue à la place?

Robuste
la source
Faites-vous cela dans SceneDelegateou AppDelegate? Et, pourriez-vous publier un peu plus de code afin que nous puissions dupliquer?
dfd le
1
Il n'y a plus de concept 'keyWindow' dans iOS car une seule application peut avoir plusieurs fenêtres. Vous pouvez stocker la fenêtre que vous créez dans votre SceneDelegate(si vous utilisez SceneDelegate)
Sudara
1
@Sudara: Donc, si je n'ai pas encore de contrôleur de vue, mais que je veux présenter une alerte - comment le faire avec une scène? Comment obtenir la scène, afin que son rootViewController puisse être récupéré? (Donc, pour faire court: qu'est-ce que la scène équivaut au «partagé» pour UIApplication?)
Hardy

Réponses:

98

Voici ma solution:

let keyWindow = UIApplication.shared.connectedScenes
        .filter({$0.activationState == .foregroundActive})
        .map({$0 as? UIWindowScene})
        .compactMap({$0})
        .first?.windows
        .filter({$0.isKeyWindow}).first

Utilisation par exemple:

keyWindow?.endEditing(true)
Berni
la source
4
Merci - pas quelque chose de très intuitif à découvrir ... 8-)
Hardy
Pendant ce temps, j'ai testé l'approche avec l'exemple de scènes multiples ( developer.apple.com/documentation/uikit/app_and_environment/… ) et tout a fonctionné comme prévu.
berni
1
Il peut également être approprié de tester la activationStatevaleur foregroundInactiveici, ce qui dans mes tests sera le cas si une alerte est présentée.
Tiré
1
@Drew, il devrait être testé car au démarrage de l'application, le contrôleur de vue est déjà visible mais l'état estforegroundInactive
Gargo
3
Ce code produit keyWindow = nil pour moi. mattla solution est celle qui fonctionne.
Canard le
178

La réponse acceptée, bien qu'ingénieuse, pourrait être trop élaborée. Vous pouvez obtenir exactement le même résultat beaucoup plus simplement:

UIApplication.shared.windows.filter {$0.isKeyWindow}.first

Je voudrais également avertir que la dépréciation de keyWindowne doit pas être prise trop au sérieux. Le message d'avertissement complet se lit comme suit:

'keyWindow' était obsolète dans iOS 13.0: ne doit pas être utilisé pour les applications qui prennent en charge plusieurs scènes car il renvoie une fenêtre clé sur toutes les scènes connectées

Donc, si vous ne supportez pas plusieurs fenêtres sur iPad, il n'y a aucune objection à continuer et à continuer à utiliser keyWindow.

mat
la source
Comment géreriez-vous une suite comme celle-ci, let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "homeVC") as! UITabBarController UIApplication.shared.keyWindow?.rootViewController = vccar avec iOS 13 et la vue de la carte, cela devient un problème car un utilisateur après avoir dit se déconnecter sera poussé vers l'écran de connexion avec l'application principale dans la hiérarchie de la vue où il peut glisser vers le bas et retourner ce qui est problématique.
Lukas Bimba
1
@Mario Ce n'est pas la première fenêtre du tableau Windows. C'est la première fenêtre clé du tableau Windows.
mat le
1
@Mario Mais la question suppose qu'il n'y a qu'une seule scène. Le problème résolu est simplement la dépréciation d'une certaine propriété. Évidemment, la vie est beaucoup plus compliquée si vous avez en fait plusieurs fenêtres sur iPad! Si vous essayez vraiment d'écrire une application iPad à fenêtres multiples, bonne chance à vous.
mat le
1
@ramzesenok Bien sûr, cela pourrait être mieux. Mais ce n'est pas faux. Au contraire, j'ai été le premier à suggérer qu'il pourrait être suffisant de demander à l'application une fenêtre qui est la fenêtre clé, évitant ainsi la dépréciation de la keyWindowpropriété. D'où les votes positifs. Si vous ne l'aimez pas, votez contre. Mais ne me dites pas de le changer pour correspondre à la réponse de quelqu'un d'autre; cela, comme je l'ai dit, serait faux.
mat
7
Cela maintenant peut également être simplifié commeUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar
64

Améliorant légèrement l'excellente réponse de matt, c'est encore plus simple, plus court et plus élégant:

UIApplication.shared.windows.first { $0.isKeyWindow }
pommy
la source
1
Je vous remercie! Y a-t-il un moyen de faire cela dans l'objectif c?
Allenktv
1
@Allenktv NSArrayn'a malheureusement pas d'équivalent à first(where:). Vous pouvez essayer de composer un one-liner avec filteredArrayUsingPredicate:et firstObject:.
pommy le
1
@Allenktv le code a été mutilé dans la section des commentaires, j'ai donc posté un équivalent Objective-C ci-dessous.
user2002649
Le compilateur Xcode 11.2 a signalé une erreur avec cette réponse et a suggéré d'ajouter des parenthèses et son contenu à first(where:):UIApplication.shared.windows.first(where: { $0.isKeyWindow })
Yassine ElBadaoui
1
Cela maintenant peut également être simplifié commeUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar
27

Voici une méthode de détection rétrocompatible keyWindow:

extension UIWindow {
    static var key: UIWindow? {
        if #available(iOS 13, *) {
            return UIApplication.shared.windows.first { $0.isKeyWindow }
        } else {
            return UIApplication.shared.keyWindow
        }
    }
}

Usage:

if let keyWindow = UIWindow.key {
    // Do something
}
Vadim Bulavin
la source
2
C'est la réponse la plus élégante et montre à quel point les Swift extensionsont belles . 🙂
Clifton Labrum le
1
Les vérifications de disponibilité ne sont guère nécessaires, depuis windowset existent isKeyWindowdepuis iOS 2.0 et first(where:)depuis Xcode 9.0 / Swift 4 / 2017.
pommy
UIApplication.keyWindowest obsolète sur iOS 13.0: @available (iOS, introduit: 2.0, obsolète: 13.0, message: "Ne doit pas être utilisé pour les applications qui prennent en charge plusieurs scènes car il renvoie une fenêtre clé sur toutes les scènes connectées")
Vadim Bulavin
15

Pour une solution Objective-C

+(UIWindow*)keyWindow
{
    UIWindow        *foundWindow = nil;
    NSArray         *windows = [[UIApplication sharedApplication]windows];
    for (UIWindow   *window in windows) {
        if (window.isKeyWindow) {
            foundWindow = window;
            break;
        }
    }
    return foundWindow;
}
user2002649
la source
10

Idéalement, comme il est obsolète, je vous conseillerais de stocker la fenêtre dans SceneDelegate. Toutefois, si vous souhaitez une solution de contournement temporaire, vous pouvez créer un filtre et récupérer la keyWindow comme ceci.

let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
Chaitanya Ramji
la source
9

Une UIApplicationextension:

extension UIApplication {

    /// The app's key window taking into consideration apps that support multiple scenes.
    var keyWindowInConnectedScenes: UIWindow? {
        return windows.first(where: { $0.isKeyWindow })
    }

}

Usage:

let myKeyWindow: UIWindow? = UIApplication.shared.keyWindowInConnectedScenes
Daniel Storm
la source
2

essayez avec ça:

UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController!.present(alert, animated: true, completion: nil)
Anas
la source
2

Pour une solution Objective-C aussi

@implementation UIWindow (iOS13)

+ (UIWindow*) keyWindow {
   NSPredicate *isKeyWindow = [NSPredicate predicateWithFormat:@"isKeyWindow == YES"];
   return [[[UIApplication sharedApplication] windows] filteredArrayUsingPredicate:isKeyWindow].firstObject;
}

@end
MadWai
la source
2

Si vous souhaitez l'utiliser dans n'importe quel ViewController, vous pouvez simplement l'utiliser.

self.view.window
Simran Singh
la source
1
Cela a fonctionné pour moi
Sanzio Angeli
1
NSSet *connectedScenes = [UIApplication sharedApplication].connectedScenes;
for (UIScene *scene in connectedScenes) {
    if (scene.activationState == UISceneActivationStateForegroundActive && [scene isKindOfClass:[UIWindowScene class]]) {
        UIWindowScene *windowScene = (UIWindowScene *)scene;
        for (UIWindow *window in windowScene.windows) {
            UIViewController *viewController = window.rootViewController;
            // Get the instance of your view controller
            if ([viewController isKindOfClass:[YOUR_VIEW_CONTROLLER class]]) {
                // Your code here...
                break;
            }
        }
    }
}
Atul Pol
la source
1
- (UIWindow *)mainWindow {
    NSEnumerator *frontToBackWindows = [UIApplication.sharedApplication.windows reverseObjectEnumerator];
    for (UIWindow *window in frontToBackWindows) {
        BOOL windowOnMainScreen = window.screen == UIScreen.mainScreen;
        BOOL windowIsVisible = !window.hidden && window.alpha > 0;
        BOOL windowLevelSupported = (window.windowLevel >= UIWindowLevelNormal);
        BOOL windowKeyWindow = window.isKeyWindow;
        if(windowOnMainScreen && windowIsVisible && windowLevelSupported && windowKeyWindow) {
            return window;
        }
    }
    return nil;
}
Erfan
la source
0

Inspiré par la réponse de berni

let keyWindow = Array(UIApplication.shared.connectedScenes)
        .compactMap { $0 as? UIWindowScene }
        .flatMap { $0.windows }
        .first(where: { $0.isKeyWindow })
Milander
la source
0

Autant de développeurs qui demandent du code Objective C pour remplacer cette dépréciation. Vous pouvez utiliser ce code ci-dessous pour utiliser keyWindow.

+(UIWindow*)keyWindow {
    UIWindow        *windowRoot = nil;
    NSArray         *windows = [[UIApplication sharedApplication]windows];
    for (UIWindow   *window in windows) {
        if (window.isKeyWindow) {
            windowRoot = window;
            break;
        }
    }
    return windowRoot;
}

J'ai créé et ajouté cette méthode dans la AppDelegateclasse en tant que méthode de classe et je l'utilise de manière très simple ci-dessous.

[AppDelegate keyWindow];

N'oubliez pas d'ajouter cette méthode dans la classe AppDelegate.h comme ci-dessous.

+(UIWindow*)keyWindow;
Paras Joshi
la source
-1

J'ai rencontré le même problème. J'ai alloué un newWindowpour une vue, et l' ai défini. Lorsque vous avez [newWindow makeKeyAndVisible]; fini de l'utiliser, réglez [newWindow resignKeyWindow]; -le puis essayez d'afficher la fenêtre clé d'origine directement par [UIApplication sharedApplication].keyWindow.

Tout va bien sur iOS 12, mais sur iOS 13, la fenêtre principale d'origine ne peut pas être affichée normalement. Il montre un écran blanc entier.

J'ai résolu ce problème en:

UIWindow *mainWindow = nil;
if ( @available(iOS 13.0, *) ) {
   mainWindow = [UIApplication sharedApplication].windows.firstObject;
   [mainWindow makeKeyWindow];
} else {
    mainWindow = [UIApplication sharedApplication].keyWindow;
}

J'espère que ça aide.

coderChrisLee
la source