Как исправить deprecated 'keyWindow' в iOS 13 и выше (Swift)
Проблема и её причина
В iOS 13 Apple ввела поддержку нескольких окон (сцен) через UIScene. Раньше у приложения было одно главное окно, доступное через keyWindow. Теперь же окон может быть несколько (например, на iPad), и свойство keyWindow стало неоднозначным. Поэтому его объявили устаревшим. Однако на iPhone в 99% случаев у вас всё ещё одно окно, и задача — получить его правильно.
Решение для iOS 13+
Самый надёжный способ — получить окно через активные сцены приложения. Вот универсальная функция, которая работает на iOS 13 и выше, а также на более старых версиях (iOS 12 и ниже). Она возвращает опциональное окно.
import UIKit
extension UIApplication {
var currentKeyWindow: UIWindow? {
// Для iOS 13+ используем connectedScenes
if #available(iOS 13.0, *) {
return self.connectedScenes
.filter { $0.activationState == .foregroundActive }
.compactMap { $0 as? UIWindowScene }
.first?.windows
.filter { $0.isKeyWindow }
.first
} else {
// Для старых версий — старый способ
return self.keyWindow
}
}
}
Как использовать
Теперь в вашем коде замените UIApplication.shared.keyWindow на UIApplication.shared.currentKeyWindow:
if let keyWindow = UIApplication.shared.currentKeyWindow {
let alert = UIAlertController(title: "Ошибка", message: "Что-то пошло не так", preferredStyle: .alert)
keyWindow.rootViewController?.present(alert, animated: true)
}
Объяснение кода
- connectedScenes — массив всех сцен (окон) приложения.
- фильтр по activationState — мы берём только активные сцены, которые видны пользователю.
- compactMap { $0 as? UIWindowScene } — преобразуем сцены в тип UIWindowScene.
- .first?.windows — получаем все окна этой сцены.
- .filter { $0.isKeyWindow } — находим среди них ключевое (главное) окно.
- #available(iOS 13.0, *) — проверка версии, чтобы код работал на iOS 12 и ниже.
Этот подход полностью соответствует рекомендациям Apple и не вызывает предупреждений компилятора.
Важные замечания
- Не используйте
UIApplication.shared.windows.first— это может вернуть не то окно (например, системное или окно другого приложения). - На iPad с несколькими сценами (Split View, Slide Over) может быть несколько активных окон. В этом примере мы берём первое активное — обычно это то, с которым работает пользователь.
- Если вам нужно показать контроллер поверх всего, лучше использовать
UIApplication.shared.topMostViewController()(см. альтернативный ответ).
Альтернативный подход: поиск самого верхнего контроллера
Вместо того чтобы каждый раз искать окно, можно написать расширение для получения самого верхнего (top-most) view controller. Это часто бывает удобнее, особенно для показа алертов или модальных окон.
extension UIApplication {
func topMostViewController() -> UIViewController? {
guard let rootVC = self.currentKeyWindow?.rootViewController else { return nil }
return topMost(from: rootVC)
}
private func topMost(from vc: UIViewController) -> UIViewController {
if let presented = vc.presentedViewController {
return topMost(from: presented)
}
if let nav = vc as? UINavigationController {
return topMost(from: nav.topViewController ?? vc)
}
if let tab = vc as? UITabBarController {
return topMost(from: tab.selectedViewController ?? vc)
}
return vc
}
}
Использование:
if let topVC = UIApplication.shared.topMostViewController() {
let alert = UIAlertController(title: "Ошибка", message: "Что-то пошло не так", preferredStyle: .alert)
topVC.present(alert, animated: true)
}
Этот метод рекурсивно обходит иерархию контроллеров (навигационные, таб-бары, модальные) и возвращает тот, который видит пользователь. Он работает на всех версиях iOS, включая 13+, если вы используете функцию currentKeyWindow из первого ответа.
Минус: требует больше кода, но зато даёт прямой доступ к контроллеру, а не к окну. Выбирайте подходящий способ под свою задачу.