Get top most UIViewController

SwiftUiviewcontroller

Swift Problem Overview


I can't seem to get the top most UIViewController without access to a UINavigationController. Here is what I have so far:

UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(vc, animated: true, completion: nil)

However, it does not seem to do anything. The keyWindow and rootViewController seem to be non-nil values too, so the optional chaining shouldn't be an issue.

NOTE: It is a bad idea to do something like this. It breaks the MVC pattern.

Swift Solutions


Solution 1 - Swift

presentViewController shows a view controller. It doesn't return a view controller. If you're not using a UINavigationController, you're probably looking for presentedViewController and you'll need to start at the root and iterate down through the presented views.

if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }
    
    // topController should now be your topmost view controller
}

For Swift 3+:

if var topController = UIApplication.shared.keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }
    
    // topController should now be your topmost view controller
}

For iOS 13+

let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

if var topController = keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

// topController should now be your topmost view controller
}

Solution 2 - Swift

have this extension

Swift 2.*

extension UIApplication {
    class func topViewController(controller: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(selected)
            }
        }
        if let presented = controller?.presentedViewController {
            return topViewController(presented)
        }
        return controller
    }
}

Swift 3

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)
        }
        return controller
    }
}

You can then use this anywhere on your controller

if let topController = UIApplication.topViewController() {
    
}

Solution 3 - Swift

For swift 4 / 5 + to get topmost viewController

// MARK: UIApplication extensions

extension UIApplication {
    
    class func getTopViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        
        if let nav = base as? UINavigationController {
            return getTopViewController(base: nav.visibleViewController)
            
        } else if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
            return getTopViewController(base: selected)
            
        } else if let presented = base?.presentedViewController {
            return getTopViewController(base: presented)
        }
        return base
    }
}

How to use

if let topVC = UIApplication.getTopViewController() {
   topVC.view.addSubview(forgotPwdView)
}

Solution 4 - Swift

extension UIWindow {
    
    func visibleViewController() -> UIViewController? {
        if let rootViewController: UIViewController = self.rootViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
        }
        return nil
    }
    
    static func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
        if let navigationController = vc as? UINavigationController,
            let visibleController = navigationController.visibleViewController  {
            return UIWindow.getVisibleViewControllerFrom( vc: visibleController )
        } else if let tabBarController = vc as? UITabBarController,
            let selectedTabController = tabBarController.selectedViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: selectedTabController )
        } else {
            if let presentedViewController = vc.presentedViewController {
                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)
            } else {
                return vc
            }
        }
    }
}

Usage:

if let topController = window.visibleViewController() {
    println(topController)
}

Solution 5 - Swift

I loved @dianz's answer, and so here is the swift 3's version of it. It's basically the same thing but his was missing a curly brace and some of the syntax/variable/method names have changed. So here it is!

extension UIApplication {
	class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.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)
		}
		return base
	}
}

Usage is still the exact same though:

if let topController = UIApplication.topViewController() {
    print("The view controller you're looking at is: \(topController)")
}

Solution 6 - Swift

iOS13+ //top Most view Controller

extension UIViewController {
    func topMostViewController() -> UIViewController {
        if self.presentedViewController == nil {
            return self
        }
        if let navigation = self.presentedViewController as? UINavigationController {
            return navigation.visibleViewController!.topMostViewController()
        }
        if let tab = self.presentedViewController as? UITabBarController {
            if let selectedTab = tab.selectedViewController {
                return selectedTab.topMostViewController()
            }
            return tab.topMostViewController()
        }
        return self.presentedViewController!.topMostViewController()
    }
}

extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return UIWindow.key!.rootViewController?.topMostViewController()
    }
}

extension UIWindow {
    static var key: UIWindow? {
        if #available(iOS 13, *) {
            return UIApplication.shared.windows.first { $0.isKeyWindow }
        } else {
            return UIApplication.shared.keyWindow
        }
    }
}

//use let vc = UIApplication.shared.topMostViewController()

// End top Most view Controller

Solution 7 - Swift

Based on Dianz answer, the Objective-C version

- (UIViewController *) topViewController {
   UIViewController *baseVC = UIApplication.sharedApplication.keyWindow.rootViewController;
   if ([baseVC isKindOfClass:[UINavigationController class]]) {
       return ((UINavigationController *)baseVC).visibleViewController;
   }

   if ([baseVC isKindOfClass:[UITabBarController class]]) {
       UIViewController *selectedTVC = ((UITabBarController*)baseVC).selectedViewController;
       if (selectedTVC) {
           return selectedTVC;
       }
   }

   if (baseVC.presentedViewController) {
       return baseVC.presentedViewController;
   }
   return baseVC;
}

Solution 8 - Swift

Use this code to find top most UIViewController

func getTopViewController() -> UIViewController? {
    var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
    while topController?.presentedViewController != nil {
        topController = topController?.presentedViewController
    }
    return topController
}

Solution 9 - Swift

https://gist.github.com/db0company/369bfa43cb84b145dfd8 I did some tests on the answers and comments on this site. For me, the following works

extension UIViewController {
    func topMostViewController() -> UIViewController {
    
        if let presented = self.presentedViewController {
            return presented.topMostViewController()
        }
    
        if let navigation = self as? UINavigationController {
            return navigation.visibleViewController?.topMostViewController() ?? navigation
        }
    
        if let tab = self as? UITabBarController {
            return tab.selectedViewController?.topMostViewController() ?? tab
    }
    
        return self
    }
}

extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return self.keyWindow?.rootViewController?.topMostViewController()
    }
}

Then, get the top viewController by:

UIApplication.shared.topMostViewController()

Solution 10 - Swift

For Swift 5+, iOS 13+

extension UIViewController {
    static func topMostViewController() -> UIViewController? {
        if #available(iOS 13.0, *) {
            let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
            return keyWindow?.rootViewController?.topMostViewController()
        }
        
        return UIApplication.shared.keyWindow?.rootViewController?.topMostViewController()
    }
    
    func topMostViewController() -> UIViewController? {
        if let navigationController = self as? UINavigationController {
            return navigationController.topViewController?.topMostViewController()
        }
        else if let tabBarController = self as? UITabBarController {
            if let selectedViewController = tabBarController.selectedViewController {
                return selectedViewController.topMostViewController()
            }
            return tabBarController.topMostViewController()
        }
            
        else if let presentedViewController = self.presentedViewController {
            return presentedViewController.topMostViewController()
        }
        
        else {
            return self
        }
    }
}

Usage:

When you are getting topMostViewController without instance of UIViewController

guard let viewController = UIViewController.topMostViewController() else { return }
print(viewController)

When you are getting topMostViewController of instance of UIViewController

let yourVC = UIViewController()
guard let vc = yourVC.topMostViewController() else { return }
print(vc)

Solution 11 - Swift

Slight Variation on @AlberZou using a computed variable rather than a function

extension UIViewController {
  var topMostViewController : UIViewController {
    
    if let presented = self.presentedViewController {
      return presented.topMostViewController
    }
    
    if let navigation = self as? UINavigationController {
      return navigation.visibleViewController?.topMostViewController ?? navigation
    }
    
    if let tab = self as? UITabBarController {
      return tab.selectedViewController?.topMostViewController ?? tab
    }
    
    return self
  }
}

extension UIApplication {
  var topMostViewController : UIViewController? {
    return self.keyWindow?.rootViewController?.topMostViewController
  }
}

Then say

if let topViewControler = UIApplication.shared.topMostViewController {
    ... do stuff
}

Solution 12 - Swift

import UIKit

extension UIApplication {
    
    // MARK: Choose keyWindow as per your choice
    var currentWindow: UIWindow? {
        connectedScenes
        .filter({$0.activationState == .foregroundActive})
        .map({$0 as? UIWindowScene})
        .compactMap({$0})
        .first?.windows
        .filter({$0.isKeyWindow}).first
    }

    // MARK: Choose keyWindow as per your choice
    var keyWindow: UIWindow? {
        UIApplication.shared.windows.first { $0.isKeyWindow }
    }
    
    class func topMostViewController(base: UIViewController? = UIApplication.shared.currentWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return topMostViewController(base: nav.visibleViewController)
        }

        if let tab = base as? UITabBarController {
            let moreNavigationController = tab.moreNavigationController

            if let top = moreNavigationController.topViewController, top.view.window != nil {
                return topMostViewController(base: top)
            } else if let selected = tab.selectedViewController {
                return topMostViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topMostViewController(base: presented)
        }
        return base
    }
}

Solution 13 - Swift

Based on Bob -c above:

Swift 3.0

extension UIWindow {
    
    
    func visibleViewController() -> UIViewController? {
        if let rootViewController: UIViewController  = self.rootViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
        }
        return nil
    }
    
    class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
        
        if vc.isKind(of: UINavigationController.self) {
            
            let navigationController = vc as! UINavigationController
            return UIWindow.getVisibleViewControllerFrom( vc: navigationController.visibleViewController!)
            
        } else if vc.isKind(of: UITabBarController.self) {
            
            let tabBarController = vc as! UITabBarController
            return UIWindow.getVisibleViewControllerFrom(vc: tabBarController.selectedViewController!)
            
        } else {
            
            if let presentedViewController = vc.presentedViewController {
                
                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)
                
            } else {
                
                return vc;
            }
        }
    }
}

Solution 14 - Swift

Too many flavours but none an iterative elaborated one. Combined from the previous ones:

     func topMostController() -> UIViewController? {
        var from = UIApplication.shared.keyWindow?.rootViewController
        while (from != nil) {
            if let to = (from as? UITabBarController)?.selectedViewController {
                from = to
            } else if let to = (from as? UINavigationController)?.visibleViewController {
                from = to
            } else if let to = from?.presentedViewController {
                from = to
            } else {
                break
            }
        }
        return from
    }

Solution 15 - Swift

in SWIFT 5.2

you can use underneath code:

import UIKit

extension UIWindow {
    static func getTopViewController() -> UIViewController? {
        if #available(iOS 13, *){
            let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
            
            if var topController = keyWindow?.rootViewController {
                while let presentedViewController = topController.presentedViewController {
                    topController = presentedViewController
                }
                return topController
            }
        } else {
            if var topController = UIApplication.shared.keyWindow?.rootViewController {
                while let presentedViewController = topController.presentedViewController {
                    topController = presentedViewController
                }
                return topController
            }
        }
        return nil
    }
}

Solution 16 - Swift

I use the following code to get the topViewController-

It is backward compatible to older iOS versions & takes care of UIScene too

extension UIApplication {
    func topViewController() -> UIViewController? {
        var topViewController: UIViewController? = nil
        if #available(iOS 13, *) {
            for scene in connectedScenes {
                if let windowScene = scene as? UIWindowScene {
                    for window in windowScene.windows {
                        if window.isKeyWindow {
                            topViewController = window.rootViewController
                        }
                    }
                }
            }
        } else {
            topViewController = keyWindow?.rootViewController
        }
        while true {
            if let presented = topViewController?.presentedViewController {
                topViewController = presented
            } else if let navController = topViewController as? UINavigationController {
                topViewController = navController.topViewController
            } else if let tabBarController = topViewController as? UITabBarController {
                topViewController = tabBarController.selectedViewController
            } else {
                // Handle any other third party container in `else if` if required
                break
            }
        }
        return topViewController
    }
}

It could be used in this way:

let topController = UIApplication.shared.topViewController()
topController?.present(controllerToPresent, animated: true, completion: nil)

Solution 17 - Swift

you can define a UIViewController variable in AppDelegate, and in every viewWillAppear set the variable to self.(however dianz answer is the best answer.)

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
    appDel.currentVC = self
}

Solution 18 - Swift

To find the visible viewController in Swift 3

if let viewControllers = window?.rootViewController?.childViewControllers {
         
     let prefs = UserDefaults.standard

     if viewControllers[viewControllers.count - 1] is ABCController{
        print("[ABCController] is visible")
            
     }
}

This code find the last added or the last active controller visible.

This I have used in AppDelegate to find active view Controller

Solution 19 - Swift

> Swift 5+

Declare

func topViewController() -> UIViewController? {
    
    let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

    if var topController = keyWindow?.rootViewController {
        
        while let presentedViewController = topController.presentedViewController {
            topController = presentedViewController
        }
        
        return topController
        
    } else {
        
        return nil
        
    }
    
}

Use

topViewController()?.view.addSubview(UIView())

Solution 20 - Swift

Where did you put the code in?

I try your code in my demo, I found out, if you put the code in

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 

will fail, because key window have been setting yet.

But I put your code in some view controller's

override func viewDidLoad() {

It just works.

Solution 21 - Swift

In a very rare case, with custom segue, the top most view controller is not in a navigation stack or tab bar controller or presented, but its view is inserted to the top of key windown's subviews.

In such situation, it's necessary to check if UIApplication.shared.keyWindow.subviews.last == self.view to determine if the current view controller is the top most.

Solution 22 - Swift

  var topViewController: UIViewController? {
        guard var topViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
        while let presentedViewController = topViewController.presentedViewController {
            topViewController = presentedViewController
        }
        return topViewController
    }

Solution 23 - Swift

For anyone looking for a swift 5/iOS 13+ solution (keywindow is deprecated since iOS 13)

extension UIApplication {
    
    class func getTopMostViewController() -> UIViewController? {
        let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
        if var topController = keyWindow?.rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            return topController
        } else {
            return nil
        }
    }
}

Solution 24 - Swift

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)
        }
        return controller
    }

Solution 25 - Swift

    extension UIApplication {
        
       public var mainKeyWindow: UIWindow? {
           if #available(iOS 13, *) {
             return UIApplication.shared.connectedScenes
            .filter { $0.activationState == .foregroundActive }
            .first(where: { $0 is UIWindowScene })
            .flatMap { $0 as? UIWindowScene }?.windows
            .first(where: \.isKeyWindow)
           } else {
              return UIApplication.shared.windows.first { $0.isKeyWindow }
           }
        }
    

	public var rootViewController: UIViewController? {
        guard let keyWindow = UIApplication.shared.mainKeyWindow, let rootViewController = keyWindow.rootViewController else {
			return nil
		}
		return rootViewController
	}

    public func topViewController(controller: UIViewController? = UIApplication.shared.rootViewController) -> UIViewController? {

        if controller == nil {
            return topViewController(controller: rootViewController)
        }
        
		if let navigationController = controller as? UINavigationController {
            return topViewController(controller: navigationController.visibleViewController)
		}

		if let tabController = controller as? UITabBarController {
			if let selectedViewController = tabController.selectedViewController {
                return topViewController(controller: selectedViewController)
			}
		}

		if let presentedViewController = controller?.presentedViewController {
            return topViewController(controller: presentedViewController)
		}

		return controller
	}
}

Swift 5.2 and above

Solution 26 - Swift

The best solution for me is an extension with a function. Create a swift file with this extension

First is the UIWindow extension:

public extension UIWindow {
    var visibleViewController: UIViewController? {
        return UIWindow.visibleVC(vc: self.rootViewController)
    }
    
    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

inside that file add function

func visibleViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}

And if you want to use it, you can call it anywhere. Example:

  override func viewDidLoad() {
    super.viewDidLoad()
      if let topVC = visibleViewController() {
             //show some label or text field 
    }
}

File code is like this:

import UIKit

public extension UIWindow {
    var visibleViewController: UIViewController? {
        return UIWindow.visibleVC(vc: self.rootViewController)
    }
    
    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

func visibleViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}

Solution 27 - Swift

extension UIViewController {
    func topMostViewController() -> UIViewController {
        if self.presentedViewController == nil {
            return self
        }
        if let navigation = self.presentedViewController as? UINavigationController {
            return navigation.visibleViewController.topMostViewController()
        }
        if let tab = self.presentedViewController as? UITabBarController {
            if let selectedTab = tab.selectedViewController {
                return selectedTab.topMostViewController()
            }
            return tab.topMostViewController()
        }
        return self.presentedViewController!.topMostViewController()
    }
}

extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return self.keyWindow?.rootViewController?.topMostViewController()
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionZoytView Question on Stackoverflow
Solution 1 - SwiftrickerbhView Answer on Stackoverflow
Solution 2 - SwiftDLendeView Answer on Stackoverflow
Solution 3 - SwiftHardik ThakkarView Answer on Stackoverflow
Solution 4 - SwiftBobj-CView Answer on Stackoverflow
Solution 5 - SwiftPonyboy47View Answer on Stackoverflow
Solution 6 - SwiftdnxClubView Answer on Stackoverflow
Solution 7 - SwiftTibidaboView Answer on Stackoverflow
Solution 8 - SwiftBibin JosephView Answer on Stackoverflow
Solution 9 - SwiftAlbert ZouView Answer on Stackoverflow
Solution 10 - SwiftMuhammad WaqasView Answer on Stackoverflow
Solution 11 - SwiftRyan HeitnerView Answer on Stackoverflow
Solution 12 - SwiftRakesh KusumaView Answer on Stackoverflow
Solution 13 - SwiftDanielView Answer on Stackoverflow
Solution 14 - SwiftJaime AgudoView Answer on Stackoverflow
Solution 15 - SwiftmohsenView Answer on Stackoverflow
Solution 16 - SwiftatulkhatriView Answer on Stackoverflow
Solution 17 - SwiftArash JamshidiView Answer on Stackoverflow
Solution 18 - SwiftPrateekroView Answer on Stackoverflow
Solution 19 - SwiftSaharat SittipanyaView Answer on Stackoverflow
Solution 20 - SwiftTinyfoolView Answer on Stackoverflow
Solution 21 - SwiftBabyPandaView Answer on Stackoverflow
Solution 22 - SwiftDenView Answer on Stackoverflow
Solution 23 - SwiftVirendraView Answer on Stackoverflow
Solution 24 - SwiftsalubhayoView Answer on Stackoverflow
Solution 25 - SwiftSuhit PatilView Answer on Stackoverflow
Solution 26 - SwifttBugView Answer on Stackoverflow
Solution 27 - SwiftMuhammad QasimView Answer on Stackoverflow