Avatar ·

Как исправить предупреждение 'keyWindow' was deprecated in iOS 13.0 в Swift?

Всем привет! Я начинающий iOS-разработчик, пишу приложение на Swift. После обновления Xcode и перехода на iOS 13 я столкнулся с проблемой. Раньше я спокойно получал доступ к главному окну приложения через `UIApplication.shared.keyWindow`, но теперь компилятор выдает предупреждение: ``` 'keyWindow' was deprecated in iOS 13.0: Use UIWindowScene.windows on a relevant window scene instead ``` Вот мой код, где я использую `keyWindow` для отображения индикатора загрузки поверх всего интерфейса: ```swift func showLoadingIndicator() { if let window = UIApplication.shared.keyWindow { let loadingView = UIView(frame: window.bounds) loadingView.backgroundColor = UIColor.black.withAlphaComponent(0.5) // добавляю индикатор window.addSubview(loadingView) } } ``` Приложение падает с ошибкой при попытке использовать `keyWindow` на симуляторе iOS 13. Как правильно получать доступ к окну в новой архитектуре с UIScene? Нужно ли что-то менять в AppDelegate или SceneDelegate? Заранее спасибо за помощь! P.S. Проект создавался на Xcode 11, используется Swift 5. Сценарий (Scene) включен по умолчанию. <<>>

Проблема и её корень

Предупреждение 'keyWindow' was deprecated in iOS 13.0 связано с переходом Apple на многоконную архитектуру через UIScene. Начиная с iOS 13, приложение может иметь несколько окон (сцен), и понятие "главного окна" (keyWindow) стало неопределённым. Вместо этого нужно работать с окнами конкретной сцены (UIWindowScene).

Решение: получение окна через UIWindowScene

Самый надёжный способ — получить окно из активной сцены. Вот обновлённая версия вашего метода:

func showLoadingIndicator() {
    // 1. Получаем активную сцену (окно)
    guard let windowScene = UIApplication.shared.connectedScenes
        .compactMap({ $0 as? UIWindowScene })
        .first(where: { $0.activationState == .foregroundActive }),
          let window = windowScene.windows.first else {
        print("Нет активного окна")
        return
    }
    
    // 2. Создаём и добавляем вью
    let loadingView = UIView(frame: window.bounds)
    loadingView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
    window.addSubview(loadingView)
}

Объяснение по шагам:

  • UIApplication.shared.connectedScenes — возвращает все подключённые сцены (включая фоновые и активные).
  • .compactMap({ $0 as? UIWindowScene }) — фильтрует только сцены типа UIWindowScene (игнорирует, например, CPTemplateApplicationScene для CarPlay).
  • .first(where: { $0.activationState == .foregroundActive }) — выбирает активную на переднем плане сцену (самую релевантную для пользователя).
  • windows.first — берёт первое окно этой сцены (обычно оно одно).

Альтернативный подход: через SceneDelegate

Если вам нужно часто обращаться к окну, можно сохранить ссылку на него в SceneDelegate:

// В SceneDelegate.swift
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?.makeKeyAndVisible()
    }
}

Затем в любом месте приложения:

if let window = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.delegate as? SceneDelegate {
    window.showLoadingIndicator() // или window?.window?.addSubview(...)
}

Важные замечания:

  • Не используйте UIApplication.shared.windows.first — это свойство тоже устарело и может вернуть окно из неактивной сцены.
  • Если приложение поддерживает мультизадачность (iPad), убедитесь, что вы работаете именно с тем окном, которое видит пользователь.
  • Для простых случаев (одно окно) можно использовать замыкание UIApplication.shared.connectedScenes.first, но это менее надёжно.

После замены кода предупреждение исчезнет, и приложение будет корректно работать на iOS 13+.

<<>>

Быстрое решение для однооконных приложений

Если ваше приложение точно не поддерживает несколько сцен (например, вы не используете мультизадачность на iPad), можно воспользоваться упрощённым подходом через UIWindowScene:

func showLoadingIndicator() {
    guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
          let window = scene.windows.first else {
        return
    }
    
    let loadingView = UIView(frame: window.bounds)
    loadingView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
    window.addSubview(loadingView)
}

Этот код просто берёт первую подключённую сцену. Он короче, но менее надёжен: если сцена ещё не активирована (например, приложение в фоне), windows.first может вернуть nil.

Когда использовать:

  • Приложение только для iPhone (одно окно).
  • Быстрый рефакторинг старого кода.
  • Прототипирование или учебные проекты.

Недостатки:

  • Может вернуть окно из фоновой сцены (например, если пользователь свернул приложение).
  • Не подходит для iPad с Split View или Slide Over.

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