Reputation: 601
I have an existing method to get the top-most view controller in iOS applications without multiple scenes, which works perfectly for single window applications on both iPhone and iPad:
extension UIApplication {
// Get top most view controller in App.
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
}
}
However, with the introduction of multiple scenes (UIScene) in iOS 13, I need a way to get the top-most view controller that can handle multiple windows and scenes. I adapted the function as follows:
extension UIApplication {
class func getTopViewController(base: UIViewController? = keyWindow?.rootViewController) -> UIViewController? {
// Find the most visible view controller in the hierarchy
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? {
// Get connected scenes
return UIApplication.shared.connectedScenes
// Keep only active scenes, onscreen and visible to the user
.filter { $0.activationState == .foregroundActive }
// Keep only the first `UIWindowScene`
.compactMap { $0 as? UIWindowScene }
// Get its associated windows
.flatMap { $0.windows }
// Finally, keep only the key window
.first { $0.isKeyWindow }
}
}
This works fine most of the time, but occasionally, keyWindow is nil on iPhone. I tried adding a 0.5 seconds delay and making the function asynchronous, which worked, but I want a synchronous solution that reliably works for both single and multiple scenes/windows.
How can I modify this function to always get the correct top-most view controller in a synchronous manner?
Additional Context:
I am targeting iOS 13 and above.
Note: First and foremost i want the second function to work on iPhone in every case currently when camera permission dialog closes and i immediately call the second function on dialog's callback, then this issue occurs, where keywindow is nil.
Upvotes: 0
Views: 79