«keyWindow» устарел. Что делать?
Если вы когда-нибудь писали:
guard let window = UIApplication.shared.keyWindow else { return }
…то наверняка видели жёлтое предупреждение Xcode: «'keyWindow' was deprecated in iOS 13.0». Это один из самых частых вопросов на Stack Overflow (более 100 000 просмотров), и не зря — Apple действительно изменила правила игры.
В этой статье мы разберём, почему это произошло, и сравним 4 подхода, как получить окно приложения без deprecated API. Вы узнаете, какой способ подходит для UIKit, SwiftUI, SceneDelegate и когда стоит использовать каждый из них. Поехали!
Почему Apple отказалась от keyWindow?
До iOS 13 приложение имело одно главное окно — keyWindow. Всё было просто: берём его и работаем. Но с приходом iPadOS и многозадачности (Split View, Slide Over) у приложения может быть несколько окон одновременно. Каждое окно управляется своим UIWindowScene. Понятие «ключевого окна» стало размытым.
Apple рекомендует теперь получать окно через сцену (UIWindowScene), к которой принадлежит ваш контроллер или вью.
Сравнение подходов: от простого к правильному
Мы рассмотрим 4 способа, от «быстро заткнуть предупреждение» до «делаем по-Apple». Выберите тот, который подходит под вашу архитектуру.
1. Быстрый фикс: UIApplication.shared.windows.first(where:)
Самый популярный ответ на Stack Overflow — использовать свойство windows вместо keyWindow:
if let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) {
// используем window
}
Плюсы:
- Минимум изменений в коде
- Работает на iOS 13+
Минусы:
- По-прежнему небезопасно при нескольких окнах
- Может вернуть nil, если окно ещё не создано
- Apple всё ещё помечает это как «deprecated» в некоторых контекстах
Когда использовать: Только для быстрого прототипа или если у вас точно одно окно (например, iPhone-приложение без мультиоконности).
2. Через SceneDelegate: правильный путь для UIKit
Начиная с iOS 13, Apple внедрила UISceneDelegate. В методе scene(_:willConnectTo:options:) вы получаете UIWindowScene и можете сохранить ссылку на окно.
Шаг 1: В SceneDelegate.swift объявите свойство window:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
}
}
Шаг 2: Получайте окно через сцену:
if let windowScene = view.window?.windowScene,
let window = windowScene.windows.first {
// используем window
}
Или, если вам нужно главное окно в любом месте приложения, храните ссылку на SceneDelegate и его window.
Плюсы:
- Полностью соответствует рекомендациям Apple
- Корректно работает с несколькими окнами
- Безопасно для многозадачности на iPad
Минусы:
- Требует настройки SceneDelegate (если вы ещё не перешли)
- Чуть больше кода
3. Через UIWindowScene.activeWindow (iOS 15+)
Начиная с iOS 15, у UIWindowScene есть свойство keyWindow, но оно тоже deprecated! Вместо этого используйте UIWindowScene.windows.first(where: { $0.isKeyWindow }).
Однако есть более элегантный способ — получить активное окно через UIApplication.shared.connectedScenes:
func getActiveWindow() -> UIWindow? {
return UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.first { $0.activationState == .foregroundActive }
.flatMap { $0.windows.first { $0.isKeyWindow } }
}
Плюсы:
- Не зависит от SceneDelegate напрямую
- Учитывает активную сцену
Минусы:
- Более сложный код
- Может вернуть nil, если сцена не активна
4. Для SwiftUI: EnvironmentValues
Если вы пишете на SwiftUI, не используйте UIKit для получения окна. Вместо этого используйте @Environment(\.window) или UIApplication.shared.connectedScenes внутри UIViewRepresentable.
Пример для SwiftUI (iOS 15+):
struct ContentView: View {
@Environment(\.window) var window: UIWindow?
var body: some View {
Text("Hello, world!")
.onAppear {
if let window = window {
print("Got window: \(window)")
}
}
}
}
Если нужно получить окно для показа модального контроллера или тоста, используйте UIApplication.shared.connectedScenes внутри UIViewRepresentable.
Таблица сравнения подходов
| Подход | iOS версия | Сложность | Мультиоконность | Рекомендация |
|---|---|---|---|---|
| UIApplication.shared.windows.first(where:) | 13+ | Низкая | Нет | Только для прототипов |
| SceneDelegate.window | 13+ | Средняя | Да | Лучший для UIKit |
| connectedScenes + activationState | 15+ | Высокая | Да | Для сложных случаев |
| SwiftUI Environment | 15+ |