Como resolver: 'keyWindow' foi descontinuado no iOS 13.0

114

Estou usando o Core Data com Cloud Kit e, portanto, tenho que verificar o status do usuário do iCloud durante a inicialização do aplicativo. Em caso de problemas, desejo emitir um diálogo para o usuário, e faço isso usando UIApplication.shared.keyWindow?.rootViewController?.present(...)até agora.

No Xcode 11 beta 4, há agora uma nova mensagem de suspensão de uso, informando:

'keyWindow' foi descontinuado no iOS 13.0: não deve ser usado para aplicativos que suportam várias cenas, pois retorna uma janela principal em todas as cenas conectadas

Como devo apresentar o diálogo em vez disso?

Hardy
fonte
Você está fazendo isso em SceneDelegateou AppDelegate? E, você poderia postar um pouco mais de código para que possamos duplicar?
dfd
1
Não existe mais o conceito de 'keyWindow' no iOS, pois um único aplicativo pode ter várias janelas. Você pode armazenar a janela que criar em seu SceneDelegate(se estiver usando SceneDelegate)
Sudara
1
@Sudara: Então, se eu ainda não tenho um controlador de visualização, mas quero apresentar um alerta - como fazer isso com uma cena? Como obter a cena, para que seu rootViewController possa ser recuperado? (Portanto, para resumir: qual é a cena equivalente ao "compartilhado" para UIApplication?)
Hardy

Respostas:

95

Esta é a minha solução:

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

Uso, por exemplo:

keyWindow?.endEditing(true)
Berni
fonte
4
Obrigado - não é algo muito intuitivo de descobrir ... 8-)
Hardy
Enquanto isso, testei a abordagem com a amostra de cena múltipla ( developer.apple.com/documentation/uikit/app_and_environment/… ) e tudo funcionou conforme o esperado.
Berni
1
Também pode ser apropriado testar o activationStatevalor foregroundInactiveaqui, o que em meu teste será o caso se um alerta for apresentado.
Desenhou em
1
@Drew deve ser testado porque no início do aplicativo o controlador de visualização já está visível, mas o estado éforegroundInactive
Gargo 01 de
2
Este código produz keyWindow = nil para mim. mattsolução é aquela que funciona.
Pato
170

A resposta aceita, embora engenhosa, pode ser excessivamente elaborada. Você pode obter exatamente o mesmo resultado de forma muito mais simples:

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

Também gostaria de advertir que a depreciação de keyWindownão deve ser levada muito a sério. A mensagem de aviso completa é:

'keyWindow' foi descontinuado no iOS 13.0: não deve ser usado para aplicativos que suportam várias cenas, pois retorna uma janela principal em todas as cenas conectadas

Portanto, se você não oferece suporte a várias janelas no iPad, não há objeção em prosseguir e continuar usando keyWindow.

mate
fonte
Como você lidaria com uma segue como esta, let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "homeVC") as! UITabBarController UIApplication.shared.keyWindow?.rootViewController = vcporque com o iOS 13 e a visualização do cartão isso se torna um problema porque um usuário após dizer que sair será enviado para a tela de login com o aplicativo principal na hierarquia de visualização, onde pode deslizar para baixo e retornar qual é problemático.
Lukas Bimba
1
@Mario Não é a primeira janela no array windows. É a primeira janela chave no array windows.
domingo
1
@Mario Mas a pergunta pressupõe que haja apenas uma cena. O problema a ser resolvido é apenas a depreciação de uma determinada propriedade. Obviamente, a vida é muito mais complicada se você realmente tiver várias janelas no iPad! Se você está realmente tentando escrever um aplicativo de múltiplas janelas para iPad, boa sorte para você.
mate
1
@ramzesenok Claro que poderia ser melhor. Mas não está errado. Ao contrário, fui o primeiro a sugerir que poderia ser suficiente solicitar ao aplicativo uma janela que seja a janela principal, evitando assim a depreciação da keyWindowpropriedade. Daí os votos positivos. Se você não gosta, vote contra ele. Mas não me diga para mudar para corresponder à resposta de outra pessoa; isso, como eu disse, estaria errado.
mate
5
Isso agora também pode ser simplificado comoUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar
62

Melhorando ligeiramente a excelente resposta de matt, esta é ainda mais simples, mais curta e mais elegante:

UIApplication.shared.windows.first { $0.isKeyWindow }
pommy
fonte
1
Obrigado! Existe uma maneira de fazer isso no objetivo c?
Allenktv
1
@Allenktv Infelizmente NSArraynão tem um equivalente a first(where:). Você pode tentar compor um one-liner com filteredArrayUsingPredicate:e firstObject:.
Pommy,
1
@Allenktv o código foi distorcido na seção de comentários, então postei um equivalente em Objective-C abaixo.
user2002649
O compilador Xcode 11.2 relatou um erro com esta resposta e sugeriu adicionar parênteses e seu conteúdo a first(where:):UIApplication.shared.windows.first(where: { $0.isKeyWindow })
Yassine ElBadaoui
1
Isso agora também pode ser simplificado comoUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar
25

Aqui está uma maneira compatível com versões anteriores de detectar 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
fonte
2
Esta é a resposta mais elegante e demonstra como os Swift extensionsão bonitos . 🙂
Clifton Labrum
1
As verificações de disponibilidade são dificilmente necessário, uma vez windowse isKeyWindowexistem desde iOS 2.0, e first(where:)desde Xcode 9.0 / Swift 4/2017
pommy
UIApplication.keyWindowfoi descontinuado no iOS 13.0: @available (iOS, introduzido: 2.0, descontinuado: 13.0, mensagem: "Não deve ser usado para aplicativos que suportam várias cenas, pois retorna uma janela principal em todas as cenas conectadas")
Vadim Bulavin
14

Para uma solução 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;
}
user2002649
fonte
10

Idealmente, uma vez que foi preterido, eu aconselharia você a armazenar a janela no SceneDelegate. No entanto, se quiser uma solução temporária, você pode criar um filtro e recuperar a keyWindow assim.

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

Uma UIApplicationextensão:

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
fonte
2

tente com isso:

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

Para uma solução Objective-C também

@implementation UIWindow (iOS13)

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

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

Se você quiser usá-lo em qualquer ViewController, basta usar.

self.view.window
Simran Singh
fonte
0

Inspirado pela resposta de Berni

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

Como muitos desenvolvedores que pedem o código Objective C da substituição desta depreciação. Você pode usar o código abaixo para usar o keyWindow.

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

Criei e adicionei este método na AppDelegateclasse como um método de classe e utilizo-o de uma forma muito simples que está abaixo.

[AppDelegate keyWindow];

Não se esqueça de adicionar este método na classe AppDelegate.h como abaixo.

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

Eu encontrei o mesmo problema. Eu aloquei um newWindowpara uma visão e configurei-o. [newWindow makeKeyAndVisible]; Quando terminar de usá-lo, configure-o [newWindow resignKeyWindow]; e tente mostrar a janela-chave original diretamente por[UIApplication sharedApplication].keyWindow .

Está tudo bem no iOS 12, mas no iOS 13 a janela-chave original não pode ser exibida normalmente. Mostra toda uma tela branca.

Resolvi este problema:

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

Espero que ajude.

coderChrisLee
fonte