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?


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)

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.


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 }

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
}

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

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

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

2

versuche es damit:

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

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

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

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

1

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

self.view.window

0

Inspiriert von der Antwort von Berni

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

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;

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.