Як вирішити проблему: "keyWindow" застаріло в iOS 13.0


119

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

У Xcode 11 beta 4 тепер є нове повідомлення про припинення використання, яке повідомляє мені:

'keyWindow' застаріло в iOS 13.0: Не слід використовувати для програм, які підтримують кілька сцен, оскільки повертає ключове вікно для всіх підключених сцен

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


Ви робите це в SceneDelegateабо AppDelegate? І, чи можете ви розмістити трохи більше коду, щоб ми могли дублювати?
dfd

1
В iOS більше не існує концепції "keyWindow", оскільки окрема програма може мати кілька вікон. Ви можете зберегти вікна ви створюєте у вашому SceneDelegate(якщо ви використовуєте SceneDelegate)
Sudara

1
@Sudara: Отже, якщо у мене ще немає контролера перегляду, але я хочу подати попередження - як це зробити зі сценою? Як отримати сцену, щоб можна було отримати її rootViewController? (Отже, якщо коротко: яка сцена еквівалентна "спільній" для застосування UIA?)
Харді

Відповіді:


98

Це моє рішення:

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

Використання, наприклад:

keyWindow?.endEditing(true)

4
Дякую - не те, що дуже інтуїтивно зрозуміти ... 8-)
Харді

Тим часом я протестував підхід на зразку декількох сцен ( developer.apple.com/documentation/uikit/app_and_environment/… ) і все працювало, як очікувалося.
berni

1
Також може бути доречним перевірити тут activationStateзначення foregroundInactive, що під час мого тестування буде, якщо буде подано попередження.
Дрю

1
@Drew його слід протестувати, оскільки при запуску програми контролер перегляду вже видно, але станforegroundInactive
Гарго

3
Цей код видає keyWindow = nil для мене. mattрішенням є те, що працює.
Качка

178

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

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 та переглядом картки це стає проблемою, оскільки користувач після виходу з системи виводиться на екран входу з головним додатком в ієрархії подання, де він може провести пальцем вниз і повернути який є проблематичним.
Лукас Бімба,

1
@Mario Це не перше вікно в масиві windows. Це перше ключове вікно в масиві windows.
мат

1
@Mario Але питання передбачає, що є лише одна сцена. Проблема, що вирішується, - це просто знецінення певного майна. Очевидно, що життя набагато складніше, якщо у вас насправді є кілька вікон на iPad! Якщо ви дійсно намагаєтеся написати програму для iPad для декількох вікон, щастя вам.
matt

1
@ramzesenok Звичайно, може бути і краще. Але це не неправильно. Навпаки, я був першим, хто припустив, що може бути достатньо запитати у програми вікно, яке є ключовим вікном, тим самим уникаючи припинення keyWindowвласності. Звідси і голоси. Якщо вам це не подобається, проголосуйте проти. Але не кажи мені, щоб я змінив його на відповідь чужої відповіді; що, як я вже сказав, було б неправильно.
мат

7
Це тепер також можна спростити, якUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar

64

Трохи вдосконаливши чудову відповідь Matt, це ще простіше, коротше та елегантніше:

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

1
Дякую! Чи є спосіб зробити це в об'єктиві c?
Allenktv

1
@Allenktv, на жаль NSArray, не має еквівалента first(where:). Ви можете спробувати скласти однокласник з filteredArrayUsingPredicate:і firstObject:.
Поммі

1
@Allenktv код був зіпсований у розділі коментарів, тому я опублікував еквівалент Objective-C нижче.
user2002649

Компілятор Xcode 11.2 повідомив про помилку з цією відповіддю та запропонував додати дужки та їх вміст до first(where:):UIApplication.shared.windows.first(where: { $0.isKeyWindow })
Яссін ЕльБадауі

1
Це тепер також можна спростити, якUIApplication.shared.windows.first(where: \.isKeyWindow)
dadalar

27

Ось зворотно сумісний спосіб виявлення 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
}

2
Це найелегантніша відповідь і демонструє, які гарні Свіфти extension. 🙂
Кліфтон

1
Перевірки готовності навряд чи потрібні, так windowsі isKeyWindowнавколо з прошивкою 2.0, а first(where:)так як Xcode 9.0 / Swift 4 / 2017.
англієць , іммігрував в Австралію

UIApplication.keyWindowбуло припинено на iOS 13.0: @available (iOS, представлено: 2.0, застаріло: 13.0, повідомлення: "Не слід використовувати для програм, що підтримують кілька сцен, оскільки повертає ключове вікно для всіх підключених сцен")
Вадим Булавін

16

Для рішення 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;
}

16

Зазвичай використовують

Стрімкий 5

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

Додатково , у UIViewController:

self.view.window

view.window є поточним вікном для цін

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

Ключові Windows

  • Відстежуйте вікна вручну

10

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

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

9

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

2

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

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

2

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

@implementation UIWindow (iOS13)

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

@end

2

Якщо ви хочете використовувати його в будь-якому ViewController, ви можете просто використовувати.

self.view.window

1
Це спрацювало для мене
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;
            }
        }
    }
}

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

Натхненний відповіддю берні

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

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;

-1

Я зустрічав ту саму проблему. Я виділив 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;
}

Сподіваюся, це допоможе.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.