Come risolvere: "keyWindow" è stato deprecato in iOS 13.0


114

Sto utilizzando Core Data con Cloud Kit e devo quindi controllare lo stato dell'utente iCloud durante l'avvio dell'applicazione. In caso di problemi desidero inviare una finestra di dialogo all'utente e lo faccio utilizzando UIApplication.shared.keyWindow?.rootViewController?.present(...)fino ad ora.

In Xcode 11 beta 4, ora c'è un nuovo messaggio di deprecazione, che mi dice:

'keyWindow' è stato deprecato in iOS 13.0: non deve essere utilizzato per applicazioni che supportano più scene poiché restituisce una finestra chiave su tutte le scene connesse

Come presenterò invece la finestra di dialogo?


Lo stai facendo in SceneDelegateo AppDelegate? E potresti pubblicare un po 'più di codice in modo da poterlo duplicare?
dfd

1
Non esiste più il concetto di "keyWindow" in iOS poiché una singola app può avere più finestre. Puoi memorizzare la finestra che crei nel tuo SceneDelegate(se stai usando SceneDelegate)
Sudara

1
@Sudara: Quindi, se non ho ancora un controller della vista, ma voglio presentare un avviso, come farlo con una scena? Come ottenere la scena, in modo che il suo rootViewController possa essere recuperato? (Quindi, per farla breve: qual è l'equivalente della scena di "condivisa" per l'applicazione UIA?)
Hardy

Risposte:


95

Questa è la mia soluzione:

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

Utilizzo ad esempio:

keyWindow?.endEditing(true)

4
Grazie - non è una cosa molto intuitiva da scoprire ... Cool
Hardy

Nel frattempo ho testato l'approccio con il campione di scene multiple ( developer.apple.com/documentation/uikit/app_and_environment/… ) e tutto ha funzionato come previsto.
berni

1
Potrebbe anche essere appropriato testare il activationStatevalore foregroundInactivequi, che nei miei test sarà il caso se viene presentato un avviso.
Drew

1
@Drew dovrebbe essere testato perché all'avvio dell'app il controller della vista è già visibile ma lo stato èforegroundInactive
Gargo

2
Questo codice produce keyWindow = nil per me. mattla soluzione è quella che funziona.
Anatra

170

La risposta accettata, sebbene ingegnosa, potrebbe essere eccessivamente elaborata. Puoi ottenere esattamente lo stesso risultato molto più semplicemente:

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

Vorrei inoltre avvertire che la deprecazione di keyWindownon dovrebbe essere presa troppo sul serio. Il messaggio di avviso completo recita:

'keyWindow' è stato deprecato in iOS 13.0: non deve essere utilizzato per applicazioni che supportano più scene poiché restituisce una finestra chiave su tutte le scene connesse

Quindi, se non stai supportando più finestre su iPad, non ci sono obiezioni ad andare avanti e continuare a utilizzare keyWindow.


Come gestiresti un segue come questo let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "homeVC") as! UITabBarController UIApplication.shared.keyWindow?.rootViewController = vcperché con iOS 13 e la visualizzazione della scheda questo diventa un problema perché un utente dopo aver detto di disconnettersi verrà spinto alla schermata di accesso con l'app principale nella gerarchia di visualizzazione dove possono scorrere verso il basso e tornare quale è problematico.
Lukas Bimba,

1
@ Mario Non è la prima finestra nell'array di Windows. È la prima finestra chiave nell'array di Windows.
matt

1
@Mario Ma la domanda presuppone che ci sia solo una scena. Il problema da risolvere è semplicemente la deprecazione di una certa proprietà. Ovviamente la vita è molto più complicata se hai effettivamente più finestre su iPad! Se stai davvero cercando di scrivere un'app per iPad con più finestre, buona fortuna.
matt

1
@ramzesenok Ovviamente potrebbe essere migliore. Ma non è sbagliato. Al contrario, sono stato il primo a suggerire che potrebbe essere sufficiente chiedere all'applicazione una finestra che sia la finestra chiave, evitando così il deprecamento della keyWindowproprietà. Da qui i voti positivi. Se non ti piace, dai un voto negativo. Ma non dirmi di cambiarlo in modo che corrisponda alla risposta di qualcun altro; questo, come ho detto, sarebbe sbagliato.
matt

5
Questo ora può anche essere semplificato comeUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar il

62

Migliorando leggermente l'eccellente risposta di matt, questo è ancora più semplice, più breve e più elegante:

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

1
Grazie! C'è un modo per farlo nell'obiettivo c?
Allenktv

1
@Allenktv Purtroppo NSArraynon ha un equivalente a first(where:). Puoi provare a comporre una battuta con filteredArrayUsingPredicate:e firstObject:.
pommy

1
@Allenktv il codice è stato alterato nella sezione commenti, quindi ho postato un equivalente Objective-C di seguito.
user2002649

Il compilatore Xcode 11.2 ha segnalato un errore con questa risposta e ha suggerito di aggiungere parentesi e il suo contenuto a first(where:):UIApplication.shared.windows.first(where: { $0.isKeyWindow })
Yassine ElBadaoui

1
Questo ora può anche essere semplificato comeUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar il

25

Ecco un modo compatibile con le versioni precedenti di rilevamento keyWindow:

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

Utilizzo:

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

2
Questa è la risposta più elegante e dimostra quanto siano belli gli Swift extension. 🙂
Clifton Labrum

1
I controlli di disponibilità sono appena necessari, da allora windowse isKeyWindowsono in circolazione da iOS 2.0 e first(where:)da Xcode 9.0 / Swift
4/2017.

UIApplication.keyWindowè stato deprecato su iOS 13.0: @available (iOS, introdotto: 2.0, deprecato: 13.0, messaggio: "Non dovrebbe essere utilizzato per applicazioni che supportano più scene poiché restituisce una finestra chiave su tutte le scene connesse")
Vadim Bulavin


14

Per una soluzione 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;
}

10

Idealmente, poiché è stato deprecato, ti consiglio di memorizzare la finestra in SceneDelegate. Tuttavia, se vuoi una soluzione temporanea, puoi creare un filtro e recuperare la keyWindow proprio in questo modo.

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

9

A UIApplicationestensione:

extension UIApplication {

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

}

Utilizzo:

let myKeyWindow: UIWindow? = UIApplication.shared.keyWindowInConnectedScenes

2

prova con quello:

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

Anche per una soluzione Objective-C

@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

Se vuoi usarlo in qualsiasi ViewController, puoi semplicemente usare.

self.view.window

0

Ispirato dalla risposta di berni

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

0

Come molti sviluppatori che chiedono il codice Objective C della sostituzione di questa deprecazione. Puoi usare questo codice qui sotto per usare keyWindow.

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

Ho creato e aggiunto questo metodo nella AppDelegateclasse come metodo di classe e lo uso con un modo molto semplice che è sotto.

[AppDelegate keyWindow];

Non dimenticare di aggiungere questo metodo nella classe AppDelegate.h come di seguito.

+(UIWindow*)keyWindow;

-1

Ho incontrato lo stesso problema. Ho assegnato una newWindowvista e l'ho impostata [newWindow makeKeyAndVisible]; Quando ho finito di usarla, impostala [newWindow resignKeyWindow]; e poi prova a mostrare la finestra chiave originale direttamente da [UIApplication sharedApplication].keyWindow.

Va tutto bene su iOS 12, ma su iOS 13 la finestra chiave originale non può essere mostrata normalmente. Mostra un intero schermo bianco.

Ho risolto questo problema:

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

Spero che sia d'aiuto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.