I am using the following extension to find the top most ViewController.
If alert is presented, the code above gives UIAlertController
.
How do I get top view controller under UIAlertController
?
-
Good answers may be found here: http://stackoverflow.com/q/26554894/3050403 – kelin Mar 21 '17 at 22:02
-
@Luda Did you solve this issue.. Can you please provide code for this. I am also facing the same issue..Thanks! – Steve Gear Jul 03 '18 at 12:32
-
@SteveGear Unfortunately I do not remember. Please check answers below – Luda Jul 03 '18 at 19:27
6 Answers
Create an UIApplication extension like below and UIApplication.topViewController()
will return the top most UIViewController
under UIAlertController
iOS 13+
extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.windows.first?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
if let alert = controller as? UIAlertController {
if let navigationController = alert.presentingViewController as? UINavigationController {
return navigationController.viewControllers.last
}
return alert.presentingViewController
}
return controller
}
}
iOS 12-
extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
if let alert = controller as? UIAlertController {
if let navigationController = alert.presentingViewController as? UINavigationController {
return navigationController.viewControllers.last
}
return alert.presentingViewController
}
return controller
}
}

- 113
- 1
- 5
You could check if the next viewController is UIAlertController
and if so return its parent. Something like this:
if let presented = base as? UIAlertController {
return base.presentingViewController
}
Add this in the extension you use before return.
Updated
extension UIApplication {
class func topViewController(base: UIViewController? = (UIApplication.sharedApplication().delegate as! AppDelegate).window?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
if let alert = base as? UIAlertController {
return alert.presentingViewController
}
return base
}
}

- 18,439
- 10
- 97
- 176

- 4,522
- 6
- 26
- 42
-
-
-
Are you sure you put it above `if let presented = base?.presentedViewController {` ? – Jelly Mar 29 '16 at 13:10
-
I did and the debuger didn't enter this if. But when I added if let presented = base as? UIAlertController { it did enter. Which means UIAlertController is the base – Luda Mar 29 '16 at 13:24
-
base is not UIAlertController. But base!.presentedViewController is We just need to find out who is UIAlertController ancestor – Luda Mar 29 '16 at 13:50
-
-
-
Post the code you use to show the `UIAlertController` and from which class. – Jelly Mar 29 '16 at 14:24
You can get the parent controller of UIAlertController
using its presentingViewController
property
extension UIApplication {
class func topViewController(base: UIViewController? = (UIApplication.sharedApplication().delegate as! AppDelegate).window?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let alert = base as? UIAlertController {
if let presenting = alert.presentingViewController {
return topViewController(base: presenting)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
Use these changes in your code, Not tested on XCode.

- 10,896
- 3
- 53
- 89
-
1
-
-
Sorry it was `UIAlertController` instead of `UIAlertViewController`. Updated my answer. – atulkhatri Mar 29 '16 at 14:26
-
-
@BrianOgden Can you please confirm if you are not using any third party controller as your `window.rootViewController` like any side menu sort of things? – atulkhatri Sep 24 '17 at 15:14
-
-
@atulkhatri it is crashing if i check presentingViewController in Objective-C. Any solution...Thanks! – Steve Gear Jul 03 '18 at 15:43
I used this extension to get the top most view controller under an UIAlertController, basically what I do is to stop looking for top view controller when I found one that is an UIAlertController.
extension UIApplication {
var topViewController: UIViewController? {
var viewController = keyWindow?.rootViewController
guard viewController != nil else { return nil }
var presentedViewController = viewController?.presentedViewController
while presentedViewController != nil, !(presentedViewController is UIAlertController) {
switch presentedViewController {
case let navagationController as UINavigationController:
viewController = navagationController.viewControllers.last
case let tabBarController as UITabBarController:
viewController = tabBarController.selectedViewController
default:
viewController = viewController?.presentedViewController
}
presentedViewController = viewController?.presentedViewController
}
return viewController
}
}

- 888
- 1
- 14
- 22
I think you want to push a new VC on current top visible VC which is a UIAlertController, then this UIAlertController will disappear immediately, cause pushed new VC dismiss too. Finally, you can not push a new VC.
The problem is, if you new a UIAlertView, then call show
, Cocoa Touch will initialize a new window which rootViewController is UIApplicationRotationFollowingController which presentingViewController is UIAlertController. So you cannot traverse the top most VC under UIAlertController because it exist in another window!
So if topViewController
traverse from keyWindow?.rootViewController
, find a UIAlertController
, call topViewController
again but traverse from window
what you want, such as (UIApplication.sharedApplication().delegate as! AppDelegate).window?.rootViewController

- 465
- 4
- 15
This is the correct one:
func firstApplicableViewController() -> UIViewController? {
if (self is UITabBarController) {
let tabBarController = self as? UITabBarController
return tabBarController?.selectedViewController?.firstApplicableViewController()
} else if (self is UINavigationController) {
let navigationController = self as? UINavigationController
return navigationController?.visibleViewController?.firstApplicableViewController()
} else if (self is UIAlertController) {
let presentingViewController: UIViewController = self.presentingViewController!
return presentingViewController.firstApplicableViewController()
} else if self.presentedViewController != nil {
let presentedViewController: UIViewController = self.presentedViewController!
if (presentedViewController is UIAlertController) {
return self
} else {
return presentedViewController.firstApplicableViewController()
}
} else {
return self
}
}

- 569
- 7
- 13