Jak rozwiązać problem: „keyWindow” został wycofany w iOS 13.0


140

Używam Core Data z Cloud Kit i dlatego muszę sprawdzać status użytkownika iCloud podczas uruchamiania aplikacji. W przypadku problemów chcę wywołać okno dialogowe dla użytkownika i robię to za pomocąUIApplication.shared.keyWindow?.rootViewController?.present(...) do tej pory.

W Xcode 11 beta 4 jest teraz nowy komunikat o wycofaniu, który mówi mi:

„keyWindow” został wycofany w iOS 13.0: nie powinien być używany dla aplikacji obsługujących wiele scen, ponieważ zwraca okno klucza we wszystkich połączonych scenach

Jak mam zamiast tego przedstawić okno dialogowe?


Robisz to w SceneDelegatelub AppDelegate? Czy możesz opublikować trochę więcej kodu, abyśmy mogli powielić?
dfd

1
W iOS nie ma już koncepcji „keyWindow”, ponieważ jedna aplikacja może mieć wiele okien. Możesz zapisać utworzone okno w swoim SceneDelegate(jeśli używasz SceneDelegate)
Sudara

1
@Sudara: A więc jeśli nie mam jeszcze kontrolera widoku, ale chcę przedstawić alert - jak to zrobić ze sceną? Jak zdobyć scenę, aby można było pobrać jej rootViewController? (A więc w skrócie: jaka jest Scena odpowiednikiem „udostępnionej” dla aplikacji UIApplication?)
Hardy

Odpowiedzi:


108

To jest moje rozwiązanie:

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

Użycie np:

keyWindow?.endEditing(true)

4
Dzięki - nie jest to coś, co jest bardzo intuicyjne, aby się dowiedzieć ... 8-)
Hardy

W międzyczasie przetestowałem to podejście z próbką wielu scen ( developer.apple.com/documentation/uikit/app_and_environment/ ... ) i wszystko działało zgodnie z oczekiwaniami.
berni

1
Właściwe może być również przetestowanie tutaj activationStatewartości foregroundInactive, co w moich testach będzie miało miejsce, jeśli zostanie przedstawiony alert.
Drew

1
@Drew należy to przetestować, ponieważ przy uruchomieniu aplikacji kontroler widoku jest już widoczny, ale stan jestforegroundInactive
Gargo

3
Ten kod generuje dla mnie keyWindow = nil. mattrozwiązaniem jest to, które działa.
Duck

208

Przyjęta odpowiedź, choć genialna, może być zbyt skomplikowana. Możesz uzyskać dokładnie ten sam wynik o wiele prościej:

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

Chciałbym również przestrzec, że deprecjacji keyWindownie należy traktować zbyt poważnie. Pełny komunikat ostrzegawczy brzmi:

„keyWindow” został wycofany w iOS 13.0: nie powinien być używany dla aplikacji obsługujących wiele scen, ponieważ zwraca okno klucza we wszystkich połączonych scenach

Więc jeśli nie obsługujesz wielu okien na iPadzie, nie ma nic przeciwko kontynuowaniu i dalszym korzystaniu keyWindow.


Jak poradziłbyś sobie z takim przepływem, let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "homeVC") as! UITabBarController UIApplication.shared.keyWindow?.rootViewController = vcponieważ w przypadku iOS 13 i widoku karty staje się to problemem, ponieważ użytkownik po powiedzeniu wylogowania zostanie przeniesiony do ekranu logowania z główną aplikacją w hierarchii widoków, gdzie może przesunąć w dół i powrócić jest problematyczne.
Lukas Bimba

2
@Mario To nie jest pierwsze okno w tablicy windows. To pierwsze kluczowe okno w tablicy windows.
mat

1
@Mario Ale pytanie zakłada, że ​​jest tylko jedna scena. Rozwiązanym problemem jest po prostu utrata wartości określonej właściwości. Oczywiście życie jest znacznie bardziej skomplikowane, jeśli faktycznie masz wiele okien na iPadzie! Jeśli naprawdę próbujesz napisać aplikację na iPada z wieloma oknami, powodzenia.
mat

1
@ramzesenok Oczywiście, że mogłoby być lepiej. Ale to nie jest złe. Wręcz przeciwnie, jako pierwszy zasugerowałem, że może wystarczyć poproszenie aplikacji o okno, które jest oknem klucza, unikając w ten sposób utraty wartości keyWindowwłaściwości. Stąd głosy poparcia. Jeśli ci się to nie podoba, oceń to. Ale nie mów mi, żebym go zmienił, aby pasował do cudzej odpowiedzi; to, jak powiedziałem, byłoby złe.
mat.

10
Teraz można to również uprościćUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar

75

Poprawiając nieznacznie doskonałą odpowiedź Matta, jest jeszcze prostsza, krótsza i bardziej elegancka:

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

1
Dziękuję Ci! Czy można to zrobić w celu c?
Allenktv

1
@Allenktv Niestety NSArraynie ma odpowiednika first(where:). Możesz spróbować skomponować jedną linijkę za pomocą filteredArrayUsingPredicate:i firstObject:.
pommy

1
@Allenktv kod został zniekształcony w sekcji komentarzy, więc zamieściłem poniżej odpowiednik Objective-C.
user2002649

Kompilator Xcode 11.2 zgłosił błąd w tej odpowiedzi i zasugerował dodanie nawiasu i jego zawartości do first(where:):UIApplication.shared.windows.first(where: { $0.isKeyWindow })
Yassine ElBadaoui

3
Teraz można to również uprościćUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar

30

Oto kompatybilny wstecz sposób wykrywania keyWindow:

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

Stosowanie:

if let keyWindow = UIWindow.key {
    // Do something
}

3
To najbardziej elegancka odpowiedź, która pokazuje, jak piękne są jerzyki extension. 🙂
Clifton Labrum

1
Sprawdza dostępność jest wcale konieczne, ponieważ windowsi isKeyWindowmają już od iOS 2.0 i first(where:)od 9.0 Xcode Swift / 4/2017
pommy

1
UIApplication.keyWindowzostał wycofany w iOS 13.0: @available (iOS, wprowadzono: 2.0, wycofano: 13.0, komunikat: „Nie należy używać w aplikacjach obsługujących wiele scen, ponieważ zwraca okno klucza we wszystkich połączonych scenach”)
Vadim Bulavin


16

Rozwiązanie 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;
}

Nie zapomnij dodać nullabledo deklaracji nagłówka!
Ben Leggiero

10

UIApplicationRozszerzenie:

extension UIApplication {

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

}

Stosowanie:

let myKeyWindow: UIWindow? = UIApplication.shared.keyWindowInConnectedScenes

9

Byłoby idealnie, ponieważ zostało wycofane, radziłbym przechowywać okno w SceneDelegate. Jeśli jednak chcesz tymczasowego obejścia problemu, możesz utworzyć filtr i pobrać keyWindow w ten sposób.

let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

To powinien być komentarz lub edycja odpowiedzi Matta , a nie osobna odpowiedź
Ben Leggiero

4

spróbuj z tym:

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

To powinien być komentarz lub edycja odpowiedzi Matta , a nie osobna odpowiedź
Ben Leggiero

Działa niesamowicie, dzięki!
Lemon


3

Również dla rozwiązania Objective-C

@implementation UIWindow (iOS13)

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

@end

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

0

Spotkałem ten sam problem. Przydzieliłem a newWindowdo widoku i ustawiłem go. [newWindow makeKeyAndVisible]; Kiedy skończyłem go używać, ustaw go, [newWindow resignKeyWindow]; a następnie spróbuj bezpośrednio wyświetlić oryginalne okno-klawisza [UIApplication sharedApplication].keyWindow.

Na iOS 12 wszystko jest w porządku, ale na iOS 13 oryginalne okno klucza nie zostało normalnie pokazane. Pokazuje cały biały ekran.

Rozwiązałem ten problem poprzez:

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

Mam nadzieję, że to pomoże.


0

Zainspirowany odpowiedzią berni

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

0

Ponieważ wielu programistów prosi o kod Objective C, który zastąpił to wycofanie. Możesz użyć poniższego kodu, aby użyć keyWindow.

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

Stworzyłem i dodałem tę metodę w AppDelegateklasie jako metodę klasową i używam jej w bardzo prosty sposób, który jest poniżej.

[AppDelegate keyWindow];

Nie zapomnij dodać tej metody do klasy AppDelegate.h, jak poniżej.

+(UIWindow*)keyWindow;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.