iOS 13+多场景下同步获取顶层ViewController的可靠实现方案
适配iOS多场景的顶层ViewController获取方案优化
原有单场景实现
我有一套适用于单窗口iOS应用的顶层ViewController获取方法,在iPhone和iPad单窗口场景下表现稳定:
extension UIApplication { // 获取应用最顶层的视图控制器 class func getTopMostViewController(base: UIViewController? = UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController) -> UIViewController? { if let nav = base as? UINavigationController { return getTopMostViewController(base: nav.visibleViewController) } if let tab = base as? UITabBarController { if let selected = tab.selectedViewController { return getTopMostViewController(base: selected) } } if let presented = base?.presentedViewController { return getTopMostViewController(base: presented) } return base } }
多场景适配改写(iOS13+)
为了兼容iOS13引入的多场景(UIScene)机制,我改写了支持多窗口、多场景的版本:
extension UIApplication { class func getTopViewController(base: UIViewController? = keyWindow?.rootViewController) -> UIViewController? { // 查找层级中最可见的视图控制器 if let nav = base as? UINavigationController { return getTopViewController(base: nav.visibleViewController) } if let tab = base as? UITabBarController { if let selected = tab.selectedViewController { return getTopViewController(base: selected) } } if let presented = base?.presentedViewController { return getTopViewController(base: presented) } return base } private static var keyWindow: UIWindow? { // 获取已连接的场景 return UIApplication.shared.connectedScenes // 仅保留活跃、可见的场景 .filter { $0.activationState == .foregroundActive } // 转换为UIWindowScene类型 .compactMap { $0 as? UIWindowScene } // 获取场景关联的所有窗口 .flatMap { $0.windows } // 筛选出keyWindow .first { $0.isKeyWindow } } }
遇到的问题
改写后的方法多数情况正常,但在iPhone上偶尔会出现keyWindow为nil的问题,尤其是相机权限弹窗关闭后立即调用时。虽然通过添加0.5秒延迟的异步方法可以解决,但需要一种同步执行、可靠兼容单/多场景的实现方案。
应用目标系统为iOS13及以上。
优化后的同步实现方案
问题根源在于原keyWindow的筛选条件过于严格:权限弹窗关闭瞬间,场景激活状态可能未及时切换回.foregroundActive,或者isKeyWindow标记还未更新。调整窗口筛选逻辑,增加兜底策略即可解决:
extension UIApplication { class func getTopViewController(base: UIViewController? = topWindow?.rootViewController) -> UIViewController? { guard let base = base else { return nil } // 处理导航控制器 if let nav = base as? UINavigationController { return getTopViewController(base: nav.visibleViewController ?? nav) } // 处理标签栏控制器 if let tab = base as? UITabBarController { return getTopViewController(base: tab.selectedViewController ?? tab) } // 处理弹出的控制器 if let presented = base.presentedViewController { return getTopViewController(base: presented) } return base } private static var topWindow: UIWindow? { let allWindows = UIApplication.shared.connectedScenes .compactMap { $0 as? UIWindowScene } .flatMap { $0.windows } // 优先取keyWindow if let keyWindow = allWindows.first({ $0.isKeyWindow }) { return keyWindow } // 兜底:取普通层级、未隐藏的窗口 return allWindows.first({ $0.windowLevel == .normal && !$0.isHidden }) ?? allWindows.first } }
优化说明
- 放宽窗口筛选条件:当
isKeyWindow不存在时,自动 fallback 到普通层级(.normal)、未隐藏的窗口,最后兜底取第一个窗口,避免出现nil - 空值安全处理:在导航控制器、标签栏控制器的逻辑中增加空值兜底,防止因为可选链返回nil导致获取失败
- 移除场景状态强依赖:不再严格要求场景处于
.foregroundActive状态,覆盖权限弹窗关闭瞬间的场景状态过渡情况
内容的提问来源于stack exchange,提问作者Rehan Ali Khan
相关产品推荐
相关产品推荐

