218

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.

YetAnotherBot
  • 1,584
  • 2
  • 16
  • 26
Zoyt
  • 4,313
  • 6
  • 27
  • 44

27 Answers27

334

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
}
PhillipJacobs
  • 1,924
  • 1
  • 13
  • 25
rickerbh
  • 9,019
  • 1
  • 28
  • 34
  • 2
    Can someone explain the while loop? To me, it looks like there is nothing to loop over; I'm not even sure why this compiles. – Professor Tom Jul 16 '16 at 02:50
  • 18
    @ProfessorTom The loop continues as long as `topController.presentedViewController` returns something (i.e., the controller has a presented child controller). It's `while let` to enforce the fact that `topController.presentedViewController` must return something. If it returns nil (i.e., this is controller has no presented children), then it will stop looping. In the body of the loop, it reassigns the child as the current `topController`, and loops again, going down the view controller hierarchy. It can reassign `topController` as it's a `var` in the outer `if` statement. – rickerbh Jul 16 '16 at 02:56
  • 1
    thank you. I haven't been able to find any examples online of `while let`. There are, of course, plenty of `if let` example to be found. – Professor Tom Jul 16 '16 at 03:49
  • 1
    The `let x = somethingThatCouldBeNil` syntax is a super handy trick to use anywhere a truth value/condition could be used. If we didn't use it here, we'd have to explicitly assign a value, then test to see if it's actually there. I think it's really succinct and expressive. – rickerbh Jul 16 '16 at 03:54
  • 1
    I'm familiar with the trick, it's just a bit more difficult to reason about in while loops–for which I have found a dearth of examples–especially this one. – Professor Tom Jul 16 '16 at 04:06
  • This is not perfect – reza_khalafi Jul 01 '17 at 13:16
  • @Alfi it works for me perfectly with the newest Swift and Xcode – NoKey Jan 31 '18 at 14:52
  • @rickerbh after exception can't able to get the topController name? – Sai Kishore Aug 14 '20 at 12:25
286

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 you use this anywhere on your controller

if let topController = UIApplication.topViewController() {

}
DLende
  • 4,992
  • 1
  • 15
  • 24
  • 1
    Thanks for your extension tip :) – Thein Oct 22 '15 at 11:10
  • 5
    I attempted to make an important edit to this answer, but it got rejected (I have no idea why and the template reasons given didn't make sense): It's important to check if the nav.visibleViewController is nil before using it in the recursive call (just like how tab.selectedViewController is checked) because otherwise, if it was nil, you would get into a recursive infinite loop. – Ethan G Aug 31 '16 at 14:10
  • @EthanG According to my understanding, if nav.visibleViewController is nil, the function will return nil (drop to the last `return`). How can it get into an infinite loop? – Desmond DAI Feb 09 '17 at 03:33
  • 3
    I think it would be more logical to make this as static function of UIViewController – Leszek Zarna Feb 17 '17 at 19:06
  • The first three returns can return a nil value. To be sure it doesn't happen, an option would be to add `?? controller` at the end of these returns. For instance, in the first one, to return this: `return topViewController(controller: navigationController.visibleViewController) ?? controller` – Oriol Apr 24 '17 at 11:58
  • 1
    The 'presentedViewController' check should probably come first if you want to catch modally presented view controllers on UITabBarControllers.. – Tokuriku Nov 22 '17 at 20:43
  • @dianz For some unknown reasons, I get nil for this po topController.navigationController – Shivam Pokhriyal Nov 15 '18 at 19:08
  • Does this need to change if using a SplitViewController? – TruMan1 May 29 '19 at 11:25
  • @Tokuriku you are correct. Currently this implementation will not return the correct controller if a view controller is modally presented on a `UINavigationController`, or a `UITabBarController`. I also wonder what happens if a `UIAlertController` is presented... – Sander Saelmans Oct 07 '19 at 06:55
86

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)
}
Hardik Thakkar
  • 13,424
  • 2
  • 80
  • 72
19
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)
}
Kendall Helmstetter Gelner
  • 73,251
  • 26
  • 123
  • 148
Bobj-C
  • 5,487
  • 8
  • 44
  • 75
  • this solution looked really promising, however I tried to run this to get the view controller I'm on when I receive a push notification and it threw a nil error on the `return UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)` – Mike Jun 10 '15 at 07:44
  • @Mike you need to only use presentedViewController, not presentedViewController. presentedViewController – allaire Nov 05 '15 at 15:52
  • @allaire If you did present a modal view controller on top of a modal view controller then you need `.presentedViewController.presentedViewController`, or not? – Baran Emre Oct 30 '18 at 18:45
7

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)")
}
Community
  • 1
  • 1
Ponyboy47
  • 886
  • 9
  • 15
6

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;
}
Tibidabo
  • 20,865
  • 4
  • 85
  • 85
6

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()
Albert Zou
  • 175
  • 2
  • 4
6

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
dnxClub
  • 61
  • 1
  • 1
5

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
}
Bibin Joseph
  • 204
  • 2
  • 6
5

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
}
Prashant Tukadiya
  • 13,804
  • 3
  • 55
  • 78
Ryan Heitner
  • 11,746
  • 6
  • 65
  • 104
4

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;
            }
        }
    }
}
lrnzcig
  • 3,374
  • 4
  • 33
  • 44
Daniel
  • 1,468
  • 12
  • 16
4

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
    }
Jaime Agudo
  • 6,969
  • 4
  • 26
  • 33
4
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
    }
}
ButchMonkey
  • 1,648
  • 13
  • 23
3

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
}
Arash Jamshidi
  • 103
  • 1
  • 6
  • 1
    thanks a lot it work fine for me as the other solution when it try to get the navigationControll it return nil so i was't able to push any new vc – Amr Angry Jan 10 '18 at 09:42
  • Make sure the currentVC defined as weak reference, or you will have memory leak. – bubuxu May 08 '19 at 04:49
3

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)
Muhammad Waqas
  • 784
  • 2
  • 8
  • 21
2

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

Prateekro
  • 546
  • 1
  • 6
  • 25
1

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.

Tinyfool
  • 1,412
  • 2
  • 16
  • 40
  • It's not in `didFinishLaunchingWithOptions`. I just need this for various debug purposes. – Zoyt Oct 31 '14 at 05:32
1

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.

BabyPanda
  • 1,522
  • 12
  • 18
1
  var topViewController: UIViewController? {
        guard var topViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
        while let presentedViewController = topViewController.presentedViewController {
            topViewController = presentedViewController
        }
        return topViewController
    }
Den
  • 1,835
  • 19
  • 16
1

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
        }
    }
}
Virendra
  • 75
  • 1
  • 10
1

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
    }
}
mohsen
  • 2,711
  • 1
  • 19
  • 41
0

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
}
tBug
  • 645
  • 8
  • 12
0
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()
    }
}
0
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
    }
salubhayo
  • 21
  • 5
0
 extension UIApplication {
    
    public var mainKeyWindow: UIWindow? {
        return windows.first(where: { $0.isKeyWindow }) ?? keyWindow
    }

    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

Suhit Patil
  • 10,286
  • 2
  • 42
  • 54
0

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)
atulkhatri
  • 9,359
  • 3
  • 45
  • 79
0

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())