So lösen Sie das Problem: 'keyWindow' war in iOS 13.0 veraltet

112

Ich verwende Core Data mit Cloud Kit und muss daher den iCloud-Benutzerstatus beim Start der Anwendung überprüfen. Bei Problemen möchte ich dem Benutzer einen Dialog geben, den ich UIApplication.shared.keyWindow?.rootViewController?.present(...)bisher verwende.

In Xcode 11 Beta 4 gibt es jetzt eine neue Verfallsmeldung, die mir sagt:

'keyWindow' war in iOS 13.0 veraltet: Sollte nicht für Anwendungen verwendet werden, die mehrere Szenen unterstützen, da ein Schlüsselfenster für alle verbundenen Szenen zurückgegeben wird

Wie soll ich stattdessen den Dialog präsentieren?

Winterhart
quelle
Machst du das in SceneDelegateoder AppDelegate? Und könnten Sie etwas mehr Code posten, damit wir ihn duplizieren können?
dfd
1
In iOS gibt es kein "keyWindow" -Konzept mehr, da eine einzelne App mehrere Fenster haben kann. Sie könnten das Fenster, das Sie erstellen, in Ihrem SceneDelegate(wenn Sie verwenden SceneDelegate)
Sudara
1
@ Sudara: Also, wenn ich noch keinen View Controller habe, aber eine Warnung anzeigen möchte - wie geht das mit einer Szene? Wie bekomme ich die Szene, damit ihr rootViewController abgerufen werden kann? (Um es kurz zu machen: Was ist die Szene, die der "geteilten" für UIApplication entspricht?)
Hardy

Antworten:

92

Das ist meine Lösung:

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

Verwendung zB:

keyWindow?.endEditing(true)
berni
quelle
4
Danke - nicht sehr intuitiv herauszufinden ... 8-)
Hardy
In der Zwischenzeit habe ich den Ansatz mit dem Beispiel für mehrere Szenen ( developer.apple.com/documentation/uikit/app_and_environment/… ) getestet und alle haben wie erwartet funktioniert.
Berni
1
Es kann auch angebracht sein, hier auf den activationStateWert zu testen foregroundInactive, was bei meinen Tests der Fall ist, wenn eine Warnung angezeigt wird.
Drew
1
@Drew sollte getestet werden, da beim Start der App der View Controller bereits sichtbar ist, der Status jedoch lautetforegroundInactive
Gargo
2
Dieser Code erzeugt für mich keyWindow = nil. mattLösung ist die, die funktioniert.
Ente
166

Die akzeptierte Antwort ist zwar genial, aber möglicherweise zu ausführlich. Sie können viel einfacher genau das gleiche Ergebnis erzielen:

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

Ich möchte auch darauf hinweisen, dass die Abwertung von keyWindownicht zu ernst genommen werden sollte. Die vollständige Warnmeldung lautet:

'keyWindow' war in iOS 13.0 veraltet: Sollte nicht für Anwendungen verwendet werden, die mehrere Szenen unterstützen, da ein Schlüsselfenster für alle verbundenen Szenen zurückgegeben wird

Wenn Sie also nicht mehrere Fenster auf dem iPad unterstützen, gibt es keine Einwände gegen die weitere Verwendung keyWindow.

matt
quelle
Wie würden Sie mit einem solchen Übergang umgehen, let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "homeVC") as! UITabBarController UIApplication.shared.keyWindow?.rootViewController = vcda dies mit iOS 13 und der Kartenansicht zu einem Problem wird, da ein Benutzer nach dem Abmelden auf den Anmeldebildschirm mit der Haupt-App in der Ansichtshierarchie verschoben wird, wo er nach unten wischen und welche zurückgeben kann ist problematisch.
Lukas Bimba
1
@Mario Es ist nicht das erste Fenster im Windows-Array. Es ist das erste Schlüsselfenster im Windows-Array.
Matt
1
@Mario Aber die Frage setzt voraus, dass es nur eine Szene gibt. Das zu lösende Problem ist lediglich die Abwertung einer bestimmten Immobilie. Offensichtlich ist das Leben viel komplizierter, wenn Sie tatsächlich mehrere Fenster auf dem iPad haben! Wenn Sie wirklich versuchen, eine iPad-App mit mehreren Fenstern zu schreiben, wünschen wir Ihnen viel Glück.
Matt
1
@ramzesenok Natürlich könnte es besser sein. Aber es ist nicht falsch. Im Gegenteil, ich war der erste, der vorschlug, dass es ausreichen könnte, die Anwendung nach einem Fenster zu fragen, das das Schlüsselfenster ist, um so die Abwertung der keyWindowImmobilie zu vermeiden . Daher die positiven Stimmen. Wenn es dir nicht gefällt, stimme es ab. Aber sag mir nicht, ich soll es ändern, damit es der Antwort eines anderen entspricht. das wäre, wie gesagt, falsch.
Matt
4
Dies kann jetzt auch vereinfacht werden alsUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar
61

Dies ist noch einfacher, kürzer und eleganter:

UIApplication.shared.windows.first { $0.isKeyWindow }
pommy
quelle
1
Danke dir! Gibt es eine Möglichkeit, dies in Ziel c zu tun?
Allenktv
@Allenktv Hat leider NSArraykein Äquivalent zu first(where:). Sie können versuchen, einen Einzeiler mit filteredArrayUsingPredicate:und zu erstellen firstObject:.
Mama
1
@Allenktv Der Code wurde im Kommentarbereich entstellt, daher habe ich unten ein Objective-C-Äquivalent veröffentlicht.
user2002649
Der Xcode 11.2-Compiler meldete einen Fehler mit dieser Antwort und schlug vor, Klammern und deren Inhalt hinzuzufügen zu first(where:):UIApplication.shared.windows.first(where: { $0.isKeyWindow })
Yassine ElBadaoui
1
Dies kann jetzt auch vereinfacht werden alsUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar
24

Hier ist eine abwärtskompatible Methode zum Erkennen keyWindow:

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

Verwendung:

if let keyWindow = UIWindow.key {
    // Do something
}
Vadim Bulavin
quelle
2
Dies ist die eleganteste Antwort und zeigt, wie schön Swifts extensionsind. 🙂
Clifton Labrum
1
Die Verfügbarkeitsprüfung sind kaum notwendig, da windowsund isKeyWindowhaben sich seit iOS 2.0 schon, und first(where:)da Xcode 9.0 / Swift 4 / 2017.
pommy
UIApplication.keyWindowwurde unter iOS 13.0 veraltet: @available (iOS, eingeführt: 2.0, veraltet: 13.0, Meldung: "Sollte nicht für Anwendungen verwendet werden, die mehrere Szenen unterstützen, da ein Schlüsselfenster für alle verbundenen Szenen zurückgegeben wird")
Vadim Bulavin
14

Für eine Objective-C-Lösung

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

Im Idealfall würde ich Ihnen raten, das Fenster im SceneDelegate zu speichern, da es veraltet ist. Wenn Sie jedoch eine temporäre Problemumgehung wünschen, können Sie einen Filter erstellen und das keyWindow wie folgt abrufen.

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

Eine UIApplicationErweiterung:

extension UIApplication {

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

}

Verwendung:

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

versuche es damit:

UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController!.present(alert, animated: true, completion: nil)
Anas
quelle
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
quelle
1

Auch für eine Objective-C-Lösung

@implementation UIWindow (iOS13)

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

@end
MadWai
quelle
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
quelle
1

Wenn Sie es in einem ViewController verwenden möchten, können Sie es einfach verwenden.

self.view.window
Simran Singh
quelle
0

Inspiriert von der Antwort von Berni

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

Wie viele Entwickler, die nach Objective C- Code für den Ersatz dieser Ablehnung fragen . Sie können diesen Code unten verwenden, um das keyWindow zu verwenden.

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

Ich habe diese Methode AppDelegateals Klassenmethode erstellt und in die Klasse eingefügt und sie auf sehr einfache Weise verwendet (siehe unten).

[AppDelegate keyWindow];

Vergessen Sie nicht, diese Methode wie unten in der AppDelegate.h-Klasse hinzuzufügen.

+(UIWindow*)keyWindow;
Paras Joshi
quelle
-1

Ich habe das gleiche Problem getroffen. Ich habe ein newWindowfür eine Ansicht zugewiesen und festgelegt. [newWindow makeKeyAndVisible]; Wenn Sie es nicht mehr verwenden, legen Sie es fest [newWindow resignKeyWindow]; und versuchen Sie dann, das ursprüngliche Schlüsselfenster direkt von anzuzeigen [UIApplication sharedApplication].keyWindow.

Unter iOS 12 ist alles in Ordnung, aber unter iOS 13 kann das ursprüngliche Tastenfenster nicht normal angezeigt werden. Es zeigt einen ganzen weißen Bildschirm.

Ich habe dieses Problem gelöst durch:

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

Ich hoffe es hilft.

coderChrisLee
quelle