Как исправить deprecated keyWindow в iOS 13: полный гайд

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

«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+