Cómo resolverlo: 'keyWindow' quedó obsoleto en iOS 13.0

121

Estoy usando Core Data con Cloud Kit y, por lo tanto, tengo que verificar el estado del usuario de iCloud durante el inicio de la aplicación. En caso de problemas quiero emitir un diálogo al usuario, y lo hago usando UIApplication.shared.keyWindow?.rootViewController?.present(...)hasta ahora.

En Xcode 11 beta 4, ahora hay un nuevo mensaje de desaprobación que me dice:

'keyWindow' quedó obsoleto en iOS 13.0: no debe usarse para aplicaciones que admitan múltiples escenas, ya que devuelve una ventana clave en todas las escenas conectadas

¿Cómo debo presentar el diálogo en su lugar?

Resistente
fuente
¿Estás haciendo esto en SceneDelegateo AppDelegate? Y, ¿podrías publicar un poco más de código para que podamos duplicarlo?
dfd
1
Ya no existe el concepto de 'keyWindow' en iOS, ya que una sola aplicación puede tener varias ventanas. Puede almacenar la ventana que crea en su SceneDelegate(si está usando SceneDelegate)
Sudara
1
@Sudara: Entonces, si aún no tengo un controlador de vista, pero quiero presentar una alerta, ¿cómo hacerlo con una escena? ¿Cómo obtener la escena para que se pueda recuperar su rootViewController? (Entonces, para abreviar: ¿cuál es la escena equivalente a la "compartida" para la aplicación UIA?)
Hardy

Respuestas:

99

Esta es mi solucion:

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

Uso, por ejemplo:

keyWindow?.endEditing(true)
berni
fuente
4
Gracias, no es algo que sea muy intuitivo de averiguar ... 8-)
Hardy
Mientras tanto, probé el enfoque con la muestra de escenas múltiples ( developer.apple.com/documentation/uikit/app_and_environment/… ) y todo funcionó como se esperaba.
berni
1
También puede ser apropiado probar el activationStatevalor foregroundInactiveaquí, que en mi prueba será el caso si se presenta una alerta.
Dibujó el
1
@Drew debe probarse porque al iniciar la aplicación, el controlador de vista ya está visible, pero el estado esforegroundInactive
Gargo
3
Este código produce keyWindow = nil para mí. mattLa solución es la que funciona.
Pato
181

La respuesta aceptada, aunque ingeniosa, puede ser demasiado elaborada. Puede obtener exactamente el mismo resultado de manera mucho más simple:

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

También quisiera advertir que la desaprobación de keyWindowno debe tomarse demasiado en serio. El mensaje de advertencia completo dice:

'keyWindow' quedó obsoleto en iOS 13.0: no debe usarse para aplicaciones que admitan múltiples escenas, ya que devuelve una ventana clave en todas las escenas conectadas

Por lo tanto, si no admite varias ventanas en iPad, no hay objeción a seguir adelante y continuar usándolo keyWindow.

mate
fuente
¿Cómo manejaría una transición como esta let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "homeVC") as! UITabBarController UIApplication.shared.keyWindow?.rootViewController = vcporque con iOS 13 y la vista de tarjeta esto se convierte en un problema porque un usuario después de decir que cierra la sesión será empujado a la pantalla de inicio de sesión con la aplicación principal en la jerarquía de vista donde puede deslizar hacia abajo y devolver cuál? es problemático.
Lukas Bimba
1
@Mario No es la primera ventana en la matriz de Windows. Es la primera ventana clave en la matriz de Windows.
Matt
1
@Mario Pero la pregunta presupone que solo hay una escena. El problema que se resuelve es simplemente la depreciación de una determinada propiedad. ¡Obviamente la vida es mucho más complicada si realmente tienes múltiples ventanas en el iPad! Si realmente está intentando escribir una aplicación para iPad con múltiples ventanas, buena suerte.
Matt
1
@ramzesenok Por supuesto que podría ser mejor. Pero no está mal. Por el contrario, fui el primero en sugerir que podría ser suficiente pedirle a la aplicación una ventana que sea la ventana clave, evitando así la depreciación de la keyWindowpropiedad. De ahí los votos positivos. Si no te gusta, dale un voto negativo. Pero no me digas que lo cambie para que coincida con la respuesta de otra persona; eso, como dije, estaría mal.
Matt
7
Esto ahora también se puede simplificar comoUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar
64

Mejorando ligeramente la excelente respuesta de Matt, esto es aún más simple, más corto y más elegante:

UIApplication.shared.windows.first { $0.isKeyWindow }
inglés
fuente
1
¡Gracias! ¿Hay alguna forma de hacer esto en el objetivo c?
Allenktv
1
@Allenktv Desafortunadamente NSArrayno tiene un equivalente a first(where:). Puede intentar componer una sola línea con filteredArrayUsingPredicate:y firstObject:.
Pommy
1
@Allenktv, el código se estropeó en la sección de comentarios, así que publiqué un equivalente de Objective-C a continuación.
user2002649
El compilador Xcode 11.2 informó un error con esta respuesta y sugirió agregar paréntesis y su contenido a first(where:):UIApplication.shared.windows.first(where: { $0.isKeyWindow })
Yassine ElBadaoui
1
Esto ahora también se puede simplificar comoUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar
27

Aquí hay una forma de detección compatible con versiones anteriores keyWindow:

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

Uso:

if let keyWindow = UIWindow.key {
    // Do something
}
Vadim Bulavin
fuente
2
Esta es la respuesta más elegante y demuestra lo hermosos que extensionson los Swift . 🙂
Clifton Labrum
1
Las comprobaciones de disponibilidad son apenas necesario, ya windowsy isKeyWindowhan existido desde iOS 2.0, y first(where:)desde Xcode 9.0 / Swift 4 / 2017.
pommy
UIApplication.keyWindowha quedado en desuso en iOS 13.0: @disponible (iOS, introducido: 2.0, en desuso: 13.0, mensaje: "No debe usarse para aplicaciones que admitan múltiples escenas, ya que devuelve una ventana clave en todas las escenas conectadas")
Vadim Bulavin
16

Para una solución 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;
}
usuario2002649
fuente
10

Idealmente, dado que ha quedado obsoleto, le aconsejo que almacene la ventana en SceneDelegate. Sin embargo, si desea una solución temporal, puede crear un filtro y recuperar el keyWindow así.

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

Una UIApplicationextensión:

extension UIApplication {

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

}

Uso:

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

prueba con eso:

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

También para una solución Objective-C

@implementation UIWindow (iOS13)

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

@end
MadWai
fuente
2

Si desea usarlo en cualquier ViewController, simplemente puede usar.

self.view.window
Simran Singh
fuente
1
Esto funcionó para mí
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
fuente
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
fuente
0

Inspirado por la respuesta de berni

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

Como muchos de los desarrolladores que solicitan el código Objective C del reemplazo de esta depreciación. Puede usar este código a continuación para usar keyWindow.

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

Creé y agregué este método en la AppDelegateclase como un método de clase y lo uso de la manera muy simple que se muestra a continuación.

[AppDelegate keyWindow];

No olvide agregar este método en la clase AppDelegate.h como se muestra a continuación.

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

Me encontré con el mismo problema. Asigné un newWindowpara una vista, y lo configuré [newWindow makeKeyAndVisible]; Cuando termine de usarlo, configúrelo [newWindow resignKeyWindow]; y luego intente mostrar la ventana clave original directamente por [UIApplication sharedApplication].keyWindow.

Todo está bien en iOS 12, pero en iOS 13 la ventana de teclas original no se puede mostrar normalmente. Muestra una pantalla entera en blanco.

Resolví este problema por:

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

Espero eso ayude.

codificadorChrisLee
fuente