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

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

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

'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes

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

Вы делаете это в SceneDelegate или AppDelegate? И не могли бы вы опубликовать немного больше кода, чтобы мы могли дублировать?

user7014451 21.07.2019 17:38

В iOS больше нет концепции «keyWindow», так как одно приложение может иметь несколько окон. Вы можете сохранить созданное окно в своем SceneDelegate (если вы используете SceneDelegate)

Sudara 22.07.2019 05:34

@Sudara: Итак, если у меня еще нет контроллера представления, но я хочу представить предупреждение - как это сделать со сценой? Как получить сцену, чтобы можно было получить ее rootViewController? (Итак, короче: что такое сцена, эквивалентная «общему» для UIApplication?)

Hardy 22.07.2019 14:20
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
263
3
111 326
21
Перейти к ответу Данный вопрос помечен как решенный

Ответы 21

Ответ принят как подходящий

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

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

Использование, например:

keyWindow?.endEditing(true)

Спасибо - не очень интуитивно понятно... 8-)

Hardy 23.07.2019 21:08

Тем временем я протестировал этот подход на примере нескольких сцен (developer.apple.com/documentation/uikit/app_and_environment‌​/…), и все сработало, как и ожидалось.

berni 24.07.2019 10:27

Вам просто нужно получить isKeyWindow.

NSProgrammer 17.09.2019 18:26

Здесь также может быть уместно проверить значение activationStateforegroundInactive, что в моем тестировании будет иметь место, если будет представлено предупреждение.

Drew 20.12.2019 05:05

@Drew это нужно проверить, потому что при запуске приложения контроллер представления уже виден, но состояние foregroundInactive

Gargo 01.02.2020 21:17

Этот код выдает для меня keyWindow = nil. matt решение - это то, которое работает.

Duck 25.02.2020 16:36

Это решение на самом деле не работает для меня в тех случаях, когда оно вызывается во время применения applicationWillEnterForeground. Решение, которое предлагает @matt, работает.

Martin 09.09.2020 04:25

Я обновил этот ответ гораздо более производительной версией кода, которая должна дать тот же результат. Дайте мне знать, если я ошибаюсь!

Ky. 16.09.2020 23:43
.map({$0 as? UIWindowScene}).compactMap({$0}) можно заменить на .compactMap { $0 as? UIWindowScene }
Jordan H 24.07.2021 20:41

Нет, к сожалению, это неправильный ответ — он не работает, когда у вас есть две сцены рядом на iPad. У меня есть сообщение здесь: tengl.net/blog/2021/11/9/uiapplication-key-window-replacemen‌​t

Teng L 09.11.2021 18:48

правда, иногда у меня нет попыток получить доступ таким образом

Michał Ziobro 02.03.2022 15:44

Редактировать Предложение, которое я здесь делаю, устарело в iOS 15. Что теперь? Что ж, если приложение не имеет нескольких собственных окон, я полагаю, что общепринятым современным способом было бы получить первое из connectedScenes приложения, принудить к UIWindowScene и взять его первое окно. Но это почти то, что делает принятый ответ! Так что мой обходной путь на данный момент кажется довольно слабым. Тем не менее, я оставлю это в силе по историческим причинам.


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

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

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

'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes

Поэтому, если вы не поддерживаете несколько окон на iPad, не возражаете против продолжения использования keyWindow.

Как бы вы справились с таким переходом let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "homeVC") as! UITabBarController UIApplication.shared.keyWindow?.rootViewController = vc, потому что с iOS 13 и карточным представлением это становится проблемой, потому что пользователь после, скажем, выхода из системы будет перемещен на экран входа в главное приложение в иерархии представлений, где они могут провести пальцем вниз и вернуться что проблематично.

Lukas Bimba 25.09.2019 04:26

@LukasBimba Это кажется не связанным; не могли бы вы задать его как отдельный вопрос? Так будет проще помочь. Спасибо.

matt 25.09.2019 22:44

Вот вопрос: stackoverflow.com/questions/58151928/…

Lukas Bimba 29.09.2019 05:11

Привет @мат. У меня такой сценарий. У меня есть окно, открытое на весь экран, и другое окно в качестве наложения на сцену. Когда я нажимаю кнопку в полноэкранном режиме и представляю контроллер представления (путем доступа к keyWindow с использованием приведенного выше ответа), он отображается в контроллере представления окна наложения. Почему keyWindow не меняется на тот, с которым я взаимодействовал?

Rakesha Shastri 10.10.2019 11:15

@RakeshaShastri Если вы нажимаете кнопку в полноэкранном режиме, вы не нужно ключевое окно. Вам не нужно окно Любые. Вы просто представляете self (контроллер представления, которому принадлежит кнопка).

matt 10.10.2019 14:05

@матовый да. я знаю об этом. Но я делаю пользовательский переход, где у меня есть контроллер представления внутри представления. Это представление добавляется в качестве подвида к полноэкранному представлению. Так что в конечном итоге мне нужен контроллер корневого представления окна. Прямо сейчас я вызываю makeKey() в окне представления перед доступом к keyWindow.

Rakesha Shastri 10.10.2019 14:09

@RakeshaShastri Не могли бы вы задать об этом отдельный вопрос? Комментарии не место, чтобы сгладить это.

matt 10.10.2019 15:02

Как только у меня будет время, я сделаю простой, полный, проверяемый пример.

Rakesha Shastri 10.10.2019 15:08

@RakeshaShastri :) Звучит здорово

matt 10.10.2019 17:50

Привет @мат. В новом проекте с раскадровкой UIApplication.shared.windows.filter {$0.isKeyWindow}.first всегда возвращает ноль, если я пытаюсь получить к нему доступ из начального контроллера представления. почему это?

joliejuly 28.11.2019 11:11

Почему первое окно в массиве? Когда я прочитал документацию для -makeKeyAndVisible UIWindow и -windows UIApplication, мне показалось, что нам нужно последнее окно. Что мне не хватает?

Mario 03.02.2020 18:00

@Mario Mario Это не первое окно в массиве окон. Это первое окно ключ в массиве окон.

matt 03.02.2020 18:20

Я думал, что в массиве окон будут окна из всех существующих сцен, и что каждая существующая сцена может иметь ключевое окно. Если это не так, то что я неправильно понимаю? Если это так, как мы узнаем, что хотим первого, а не последнего? Спасибо!

Mario 03.02.2020 20:25

@Mario Но вопрос предполагает, что есть только одна сцена. Решаемая проблема - это просто устаревание определенного свойства. Очевидно, что жизнь намного сложнее, если у вас на самом деле несколько окон на iPad! Если вы действительно пытаетесь написать многооконное приложение для iPad, удачи вам.

matt 03.02.2020 20:46

рассмотрите возможность использования first(where:), так как это более эффективно, чем ваше решение (оно не создает промежуточный массив отфильтрованных элементов)

ramzesenok 22.02.2020 13:57

не хотел вас обидеть, просто заметил, что у вашего решения много плюсов, и многие люди будут его использовать, хотя оно могло бы быть и лучше

ramzesenok 22.02.2020 17:53

@ramzesenok Конечно, могло быть и лучше. Но это не так. Напротив, я был первым, кто предположил, что может быть достаточно запросить у приложения окно, которое является ключевым окном, тем самым избегая устаревания свойства keyWindow. Отсюда и плюсы. Если вам это не нравится, поставьте минус. Но не говорите мне изменить его, чтобы он соответствовал чужому ответу; это, как я уже сказал, было бы неправильно.

matt 22.02.2020 18:06

@Mario сказал: «Я думал [...], что каждая существующая сцена может иметь ключевое окно [...]». Документация указывает: «Только одно окно за раз может быть ключевым окном [для приложения, а не место действия]." Насколько я понимаю: даже если у вас есть несколько сцен вашего приложения, ключевое окно первый в массиве окон также является окном Только.

andreas1724 04.06.2020 00:58

Теперь это также можно упростить как UIApplication.shared.windows.first(where: \.isKeyWindow)

dadalar 04.06.2020 10:20

@dadalar Да, мне очень нравится этот синтаксис (новое в Swift 5.2).

matt 21.07.2020 21:11

@LukasBimba, вы можете заменить свой код на этот UIApplication.shared.windows.first?.rootViewController = vc или просто на этот self.view.window?.rootViewController = vc

Carlos Irano 31.08.2020 01:47

Принятый ответ на самом деле не работает для меня в тех случаях, когда он вызывается во время applicationWillEnterForeground. Решение, которое предлагает @matt, работает.

Martin 09.09.2020 04:24

Права редактирования @BenLeggiero не должны использоваться ни для замены основного содержания ответа основным содержанием другого ответа, ни для введения собственного стиля письма.

pommy 20.09.2020 10:01

@pommy Основной контент остался прежним (изменилась только производительность, а не поведение или подход), а мой стиль отличается. Как сказано на странице справки, каждый раз, когда вы видите сообщение, которое нуждается в улучшении, и склонны предложить редактирование, вы можете сделать это. [...] Ожидается, что правки будут существенными и оставят пост лучше, чем вы его нашли. Для нас с правами редактирования редактирование приветствуется!

Ky. 23.09.2020 17:51

@BenLeggiero Хорошо, но превращать мой ответ в ответ Помми не рекомендуется. Не используйте редактирование, чтобы лгать об истории. История такова, что я дал ответ, Помми дал другой ответ, возможно, лучший. Это нужно стоять.

matt 23.09.2020 18:12

Прости, что обидел тебя, @matt; это не было моим намерением. Я также не собирался лгать об истории. Я надеялся помочь людям, пришедшим сюда из Google, пытающимся найти быстрый ответ, которым может быть неинтересно читать все ответы, чтобы найти новые. Я считаю историю редактирования историей ответа, а не связанными ответами рядом с этим.

Ky. 23.09.2020 18:18

Честно говоря, я думал, что то, что я делаю, было НАСТОЛЬКО передовой практикой. Я задал вопрос в Meta, чтобы понять, что здесь произошло: meta.stackoverflow.com/q/401472/3939277

Ky. 23.09.2020 18:58

@BenLeggiero Я тоже так думал, пока не увидел, что ты делаешь мой ответ похожим на ответ Помми. Это мой ответ, поэтому мне нужно занять определенную позицию, и моя позиция такова, к лучшему или к худшему, это то, что я сказал. Я проголосовал за ответ Помми как за лучшее выражение моего. Ваше редактирование заставляет меня «украсть» его ответ, и я не хочу этого делать. Когда вы редактируете меня, вы вкладываете свои слова в рот мой, так что это нужно учитывать.

matt 23.09.2020 19:08

@matt большое спасибо, ваше простое решение решило мою большую проблему! :)

Ricardo Barroso 11.05.2021 12:45

Даже если вы являются поддерживаете несколько окон, вы должны продолжать использовать его, так как нет хорошего обходного пути. У меня есть пост, в котором обсуждались ограничения: tengl.net/blog/2021/11/9/uiapplication-key-window-replacemen‌​t Но в основном вы правы. Ваш ответ должен быть на первом месте.

Teng L 09.11.2021 18:49

iOS 15, совместимо до iOS 13

UIApplication
.shared
.connectedScenes
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
.first { $0.isKeyWindow }

Обратите внимание, что connectedScenes доступен только с iOS 13. Если вам нужна поддержка более ранних версий iOS, вы должны поместить это в оператор if #available(iOS 13, *).

Более длинный, но более понятный вариант:

UIApplication
.shared
.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap { $0.windows }
.first { $0.isKeyWindow }

iOS 13 и 14

Следующий исторический ответ все еще действителен для iOS 15, но его следует заменить, поскольку UIApplication.shared.windows устарел. Спасибо @matt за указание на это!

Оригинальный ответ:

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

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

Спасибо! Есть ли способ сделать это в задаче c?

Allenktv 21.09.2019 15:21

@Allenktv К сожалению, у NSArray нет эквивалента first(where:). Вы можете попробовать составить однострочник с filteredArrayUsingPredicate: и firstObject:.

pommy 22.09.2019 17:23

@Allenktv код был искажен в разделе комментариев, поэтому я разместил эквивалент Objective-C ниже.

user2002649 18.10.2019 11:01

Компилятор Xcode 11.2 сообщил об ошибке с этим ответом и предложил добавить скобки и их содержимое в first(where:): UIApplication.shared.windows.first(where: { $0.isKeyWindow })

Yassine ElBadaoui 03.11.2019 04:44

@YassineElBadaoui Скобки не нужны в простом присваивании 89

pommy 04.11.2019 09:12

Теперь это также можно упростить как UIApplication.shared.windows.first(where: \.isKeyWindow)

dadalar 04.06.2020 10:20

@dadalar Ваше решение с ключевым путем, возможно, более элегантно, но также немного длиннее, поскольку вы не можете отбросить метку аргумента where, как в случае закрытия замыкания.

pommy 05.06.2020 13:52

@pommy Правда, я думаю, это вопрос личного вкуса. Xcode обычно не делает отступы для конечных замыканий, поэтому мне больше нравится другой подход.

dadalar 05.06.2020 13:58

Это должно было быть опубликовано как редактирование ответа Мэтта.

Ky. 17.09.2020 23:31

Права редактирования @BenLeggiero не должны использоваться для замены основного содержания ответа основным содержанием другого ответа.

pommy 20.09.2020 09:57

@pommy Основной контент тот же. У этого та же концепция и подход к решению, но, как вы сказали в тексте, он «проще, короче и элегантнее». Как сказано на странице справки, каждый раз, когда вы видите сообщение, которое нуждается в улучшении, и склонны предложить редактирование, вы можете сделать это. [...] Ожидается, что правки будут существенными и оставят пост лучше, чем вы его нашли. Для нас с правами редактирования редактирование приветствуется!

Ky. 23.09.2020 17:47

К сожалению, windows также устарел.

matt 25.09.2021 14:47

@matt Спасибо, я обновил свой ответ. Я не могу избежать создания временных массивов, как в моем исходном ответе, но, по крайней мере, их все еще меньше, чем в принятом ответе. Я надеюсь, что оптимизация компилятора поймет, что создавать их не нужно. Kotlin’s asSequence() здесь бы пригодился.

pommy 27.09.2021 13:20

Этот метод не работает в одной ситуации: на iPad есть две сцены на разделенном экране, и обе они распознаются в этом методе как «Ключевое окно». Но UIApplication.shared.keyWindow возвращает один (и это всегда тот, с которым вы в последний раз взаимодействовали, обозначенный тремя приподнятыми точками сверху, если вы подключаетесь к клавиатуре)

Teng L 09.11.2021 18:09

@TengL вы можете просто использовать filter(\.isKeyWindow) вместо first { $0.isKeyWindow } и вернуть массив со всеми ключевыми окнами

Leo Dabus 12.03.2022 15:34

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

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

Это должен быть комментарий или редактирование ответ Мэтта, а не отдельный ответ.

Ky. 18.09.2020 18:22

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

Не забудьте добавить nullable к объявлению заголовка!

Ky. 18.09.2020 18:21

О, как я не скучаю по Objective-C :)

Sherwin Zadeh 15.10.2021 22:15

Я столкнулся с той же проблемой. Я выделил 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;
}

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

Расширение 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
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;
            }
        }
    }
}

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

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

Я проверил этот код очень странным образом, и он работал лучше остальных. Там, где другие вылетали, он работал независимо от переднего плана/фона...

shanezzar 18.09.2021 11:52

Поскольку connectScenes — это набор, необходимо ли здесь преобразование в массив?

Marcy 29.10.2021 02:59

Вот обратно совместимый способ обнаружения 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
}

Это самый элегантный ответ, демонстрирующий, насколько красивы Swift extension. ?

Clifton Labrum 02.04.2020 07:11

Проверки доступности вряд ли нужны, так как windows и isKeyWindow существуют с iOS 2.0, а first(where:) с Xcode 9.0/Swift 4/2017.

pommy 05.06.2020 14:02
UIApplication.keyWindow устарело в iOS 13.0: @ available (iOS, введено: 2.0, устарело: 13.0, сообщение: «Не следует использовать для приложений, поддерживающих несколько сцен, поскольку он возвращает ключевое окно для всех подключенных сцен»)
Vadim Bulavin 09.06.2020 09:54

@ВадимБулавин ты не понял комментария Помми предложила только использоватьstatic var key: UIWindow? { UIApplication.shared.windows.first(where: \.isKeyWindow) }

Leo Dabus 03.06.2021 22:22

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

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

Это должен быть комментарий или редактирование ответ Мэтта, а не отдельный ответ.

Ky. 18.09.2020 18:23

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

Свифт 5

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

Кроме того, в UIViewController:

self.view.window

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

WWDC 2019:

Key Windows

  • Track windows manually

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

@implementation UIWindow (iOS13)

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

@end

Как и многие разработчики, запрашивающие код Цель С замены этого устаревания. Вы можете использовать этот код ниже, чтобы использовать 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;
- (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;
}

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

self.view.window

Код Берни хорош, но он не работает, когда приложение возвращается из фона.

Это мой код:

class var safeArea : UIEdgeInsets
{
    if #available(iOS 13, *) {
        var keyWindow = UIApplication.shared.connectedScenes
                .filter({$0.activationState == .foregroundActive})
                .map({$0 as? UIWindowScene})
                .compactMap({$0})
                .first?.windows
                .filter({$0.isKeyWindow}).first
        // <FIX> the above code doesn't work if the app comes back from background!
        if (keyWindow == nil) {
            keyWindow = UIApplication.shared.windows.first { $0.isKeyWindow }
        }
        return keyWindow?.safeAreaInsets ?? UIEdgeInsets()
    }
    else {
        guard let keyWindow = UIApplication.shared.keyWindow else { return UIEdgeInsets() }
        return keyWindow.safeAreaInsets
    }
}

Часть о том, что не работает, когда приложение возвращается из фона, просто укусила меня в производстве. В отладчике он всегда возвращает окно, но при запуске вручную он регулярно дает сбой, когда приложение запускается из фона.

otusweb 22.09.2021 10:29

Я столкнулся с проблемой, когда .foregroundActive сцены были пусты

Итак, вот мой обходной путь

public extension UIWindow {
    @objc
    static var main: UIWindow {
        // Here we sort all the scenes in order to work around the case
        // when no .foregroundActive scenes available and we need to look through
        // all connectedScenes in order to find the most suitable one
        let connectedScenes = UIApplication.shared.connectedScenes
            .sorted { lhs, rhs in
                let lhs = lhs.activationState
                let rhs = rhs.activationState
                switch lhs {
                case .foregroundActive:
                    return true
                case .foregroundInactive:
                    return rhs == .background || rhs == .unattached
                case .background:
                    return rhs == .unattached
                case .unattached:
                    return false
                @unknown default:
                    return false
                }
            }
            .compactMap { $0 as? UIWindowScene }

        guard connectedScenes.isEmpty == false else {
            fatalError("Connected scenes is empty")
        }
        let mainWindow = connectedScenes
            .flatMap { $0.windows }
            .first(where: \.isKeyWindow)

        guard let window = mainWindow else {
            fatalError("Couldn't get main window")
        }
        return window
    }
}

Если ваше приложение не было обновлено, чтобы принять жизненный цикл приложения на основе сцен, другим простым способом получить активный объект окна является использование UIApplicationDelegate:

let window = UIApplication.shared.delegate?.window
let rootViewController = window??.rootViewController

У меня был ответил на вопрос о дублирующем фиде, и, поскольку я не смог найти здесь ответ с таким количеством кода (с комментариями), вот мой вклад:

(Протестировано с iOS 15.2, работающей на Xcode 13.2.1)

extension UIApplication {
    
    var keyWindow: UIWindow? {
        // Get connected scenes
        return UIApplication.shared.connectedScenes
            // Keep only active scenes, onscreen and visible to the user
            .filter { $0.activationState == .foregroundActive }
            // Keep only the first `UIWindowScene`
            .first(where: { $0 is UIWindowScene })
            // Get its associated windows
            .flatMap({ $0 as? UIWindowScene })?.windows
            // Finally, keep only the key window
            .first(where: \.isKeyWindow)
    }
    
}

Если вы хотите найти представленный UIViewController в ключе UIWindow , вот еще один extension, который вам может пригодиться:

extension UIApplication {
    
    var keyWindowPresentedController: UIViewController? {
        var viewController = self.keyWindow?.rootViewController
        
        // If root `UIViewController` is a `UITabBarController`
        if let presentedController = viewController as? UITabBarController {
            // Move to selected `UIViewController`
            viewController = presentedController.selectedViewController
        }
        
        // Go deeper to find the last presented `UIViewController`
        while let presentedController = viewController?.presentedViewController {
            // If root `UIViewController` is a `UITabBarController`
            if let presentedController = presentedController as? UITabBarController {
                // Move to selected `UIViewController`
                viewController = presentedController.selectedViewController
            } else {
                // Otherwise, go deeper
                viewController = presentedController
            }
        }
        
        return viewController
    }
    
}

Вы можете поместить это куда хотите, но я лично добавил это как extension к UIViewController.

Это позволяет мне добавлять больше полезных расширений, например, для более удобного представления UIViewController, например:

extension UIViewController {
    
    func presentInKeyWindow(animated: Bool = true, completion: (() -> Void)? = nil) {
        DispatchQueue.main.async {
            UIApplication.shared.keyWindow?.rootViewController?
                .present(self, animated: animated, completion: completion)
        }
    }
    
    func presentInKeyWindowPresentedController(animated: Bool = true, completion: (() -> Void)? = nil) {
        DispatchQueue.main.async {
            UIApplication.shared.keyWindowPresentedController?
                .present(self, animated: animated, completion: completion)
        }
    }
    
}

если вы используете SwiftLint с правилом first_where и хотите заставить замолчать враждующих сторон:

UIApplication.shared.windows.first(where: { $0.isKeyWindow })

Другие вопросы по теме