Как решить: 'keyWindow' устарело в iOS 13.0

143

Я использую Core Data с Cloud Kit и поэтому должен проверять статус пользователя iCloud во время запуска приложения. В случае возникновения проблем я хочу создать диалог с пользователем, и я делаю это UIApplication.shared.keyWindow?.rootViewController?.present(...)до сих пор.

В Xcode 11 beta 4 появилось новое сообщение об отказе от поддержки, в котором говорится:

'keyWindow' устарело в iOS 13.0: не должно использоваться для приложений, поддерживающих несколько сцен, поскольку он возвращает ключевое окно для всех связанных сцен.

Как мне представить диалог вместо этого?

Харди
источник
Вы делаете это в SceneDelegateили AppDelegate? И не могли бы вы опубликовать еще немного кода, чтобы мы могли его продублировать?
dfd
1
В iOS больше нет концепции keyWindow, так как одно приложение может иметь несколько окон. Вы можете сохранить SceneDelegateSceneDelegate
созданное
1
@Sudara: Итак, если у меня еще нет контроллера представления, но я хочу показать предупреждение - как это сделать со сценой? Как получить сцену, чтобы можно было получить ее rootViewController? (Итак, чтобы быть кратким: что такое Scene эквивалентно «shared» для UIApplication?)
Харди

Ответы:

108

Это мое решение:

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

Пример использования:

keyWindow?.endEditing(true)
Berni
источник
5
Спасибо - не то, что очень интуитивно
Харди
Тем временем я протестировал подход на примере нескольких сцен ( developer.apple.com/documentation/uikit/app_and_environment/… ), и все заработало, как ожидалось.
berni
1
Здесь также может быть уместно проверить activationStateзначение foregroundInactive, что в моем тестировании будет иметь место, если отображается предупреждение.
Дрю
1
@Drew его следует протестировать, потому что при запуске приложения контроллер представления уже виден, но состояние естьforegroundInactive
Гарго
3
Этот код создает для меня keyWindow = nil. mattрешение то, что работает.
Дак
213

Принятый ответ, хотя и гениальный, может быть излишне сложным. Вы можете получить точно такой же результат намного проще:

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

Я также хотел бы предостеречь, что keyWindowне следует воспринимать отказ от поддержки слишком серьезно. Полное предупреждение гласит:

'keyWindow' устарело в iOS 13.0: не должно использоваться для приложений, поддерживающих несколько сцен, поскольку он возвращает ключевое окно для всех связанных сцен.

Так что, если вы не поддерживаете несколько окон на iPad, нет никаких возражений против продолжения использования keyWindow.

матовый
источник
Как бы вы справились с таким переходом, let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "homeVC") as! UITabBarController UIApplication.shared.keyWindow?.rootViewController = vcпотому что с iOS 13 и карточным представлением это становится проблемой, потому что пользователь после выхода, скажем, будет переведен на экран входа в систему с основным приложением в иерархии представлений, где они могут провести вниз и вернуть то, что проблематично.
Лукас Бимба
2
@Mario Это не первое окно в массиве окон. Это первое ключевое окно в массиве окон.
Мэтт
1
@Mario Но вопрос предполагает, что есть только одна сцена. Решаемая проблема заключается в простом износе определенного имущества. Очевидно, что жизнь намного сложнее, если у вас на самом деле несколько окон на iPad! Если вы действительно пытаетесь написать приложение для iPad с несколькими окнами, удачи вам.
Мэтт
1
@ramzesenok Конечно могло быть и лучше. Но это не так. Напротив, я был первым, кто предположил, что может быть достаточно запросить у приложения окно, которое является ключевым окном, что позволит избежать устаревания keyWindowсвойства. Отсюда и положительные голоса. Если вам это не нравится, проголосуйте против. Но не просите меня изменить его, чтобы он соответствовал чьему-то ответу; это, как я сказал, было бы неправильно.
матовый
11
Теперь это также можно упростить какUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar
77

Немного улучшив отличный ответ Мэтта, он стал еще проще, короче и элегантнее:

UIApplication.shared.windows.first { $0.isKeyWindow }
pommy
источник
1
Спасибо! Есть ли способ сделать это в цели c?
Allenktv
1
@Allenktv К сожалению NSArray, не имеет эквивалента first(where:). Вы можете попробовать составить однострочник с помощью filteredArrayUsingPredicate:и firstObject:.
pommy
1
@Allenktv код был искажен в разделе комментариев, поэтому я разместил эквивалент Objective-C ниже.
user2002649
Компилятор Xcode 11.2 сообщил об ошибке в этом ответе и предложил добавить круглые скобки и его содержимое first(where:):UIApplication.shared.windows.first(where: { $0.isKeyWindow })
Яссин Эль-Бадауи
3
Теперь это также можно упростить какUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar
30

Вот способ обнаружения с обратной совместимостью keyWindow:

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

Применение:

if let keyWindow = UIWindow.key {
    // Do something
}
Вадим Булавин
источник
3
Это наиболее элегантный ответ, демонстрирующий, насколько красивы Swift extension. 🙂
Клифтон
1
Проверки доступности вряд ли необходимы, поскольку windowsи isKeyWindowсуществуют с iOS 2.0 и first(where:)с Xcode 9.0 / Swift 4/2017.
pommy
1
UIApplication.keyWindowустарела в iOS 13.0: @available (iOS, введено: 2.0, устарело: 13.0, сообщение: «Не следует использовать для приложений, поддерживающих несколько сцен, поскольку оно возвращает ключевое окно для всех связанных сцен»)
Вадим Булавин,
20

Обычно используют

Swift 5

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

Вдобавок , в UIViewController:

self.view.window

view.window текущее окно для сцен

WWDC 2019: введите описание изображения здесь

Ключевые окна

  • Отслеживайте окна вручную
iHTCboy
источник
17

Для решения 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
источник
Не забудьте добавить nullableв заголовок объявление!
Бен Легжеро,
10

UIApplicationРасширение:

extension UIApplication {

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

}

Применение:

let myKeyWindow: UIWindow? = UIApplication.shared.keyWindowInConnectedScenes
Дэниел Сторм
источник
9

В идеале, поскольку он устарел, я бы посоветовал вам сохранить окно в SceneDelegate. Однако, если вам нужен временный обходной путь, вы можете создать фильтр и получить keyWindow точно так же.

let window = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
Чайтанья Рамджи
источник
Это должно быть комментарий или отредактировать для ответа Мэтта , а не отдельный ответ
Бен Leggiero
4

попробуйте с этим:

UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController!.present(alert, animated: true, completion: nil)
Анас
источник
Это должно быть комментарий или отредактировать для ответа Мэтта , а не отдельный ответ
Бен Leggiero
Отлично работает, спасибо!
Лимон
4

Если вы хотите использовать его в любом ViewController, вы можете просто использовать.

self.view.window
Симран Сингх
источник
3

Для решения Objective-C тоже

@implementation UIWindow (iOS13)

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

@end
MadWai
источник
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

Я встретил ту же проблему. Я выделил a newWindowдля представления и установил его. [newWindow makeKeyAndVisible]; Когда закончил его использовать, установил его, [newWindow resignKeyWindow]; а затем попытался показать исходное ключевое окно напрямую [UIApplication sharedApplication].keyWindow.

На iOS 12 все в порядке, но на iOS 13 исходное ключевое окно не может отображаться нормально. Показывает весь белый экран.

Я решил эту проблему:

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

Надеюсь, это поможет.

кодерChrisLee
источник
0

Вдохновленный ответом Берни

let keyWindow = Array(UIApplication.shared.connectedScenes)
        .compactMap { $0 as? UIWindowScene }
        .flatMap { $0.windows }
        .first(where: { $0.isKeyWindow })
Milander
источник
0

Как многие разработчики просят код Objective C для замены этой устаревшей версии. Вы можете использовать этот приведенный ниже код для использования keyWindow.

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

Я создал и добавил этот метод в AppDelegateкласс как метод класса и очень просто использую его, как показано ниже.

[AppDelegate keyWindow];

Не забудьте добавить этот метод в класс AppDelegate.h, как показано ниже.

+(UIWindow*)keyWindow;
Парас Джоши
источник