How to get visible viewController from app delegate when using storyboard? - ios

I have some viewControllers, and I don't use NavigationController.
How can I get visible view controller in app delegate methods (e.g. applicationWillResignActive)?
I know how to do it from NSNotification, but I think it's the wrong way.

This should do it for you:
- (void)applicationWillResignActive:(UIApplication *)application
{
UIViewController *vc = [self visibleViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController *)visibleViewController:(UIViewController *)rootViewController
{
if (rootViewController.presentedViewController == nil)
{
return rootViewController;
}
if ([rootViewController.presentedViewController isKindOfClass:[UINavigationController class]])
{
UINavigationController *navigationController = (UINavigationController *)rootViewController.presentedViewController;
UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
return [self visibleViewController:lastViewController];
}
if ([rootViewController.presentedViewController isKindOfClass:[UITabBarController class]])
{
UITabBarController *tabBarController = (UITabBarController *)rootViewController.presentedViewController;
UIViewController *selectedViewController = tabBarController.selectedViewController;
return [self visibleViewController:selectedViewController];
}
UIViewController *presentedViewController = (UIViewController *)rootViewController.presentedViewController;
return [self visibleViewController:presentedViewController];
}

#aviatorken89's answer worked well for me. I had to translate it to Swift -
for anybody starting out with Swift:
Updated for Swift 3:
func getVisibleViewController(_ rootViewController: UIViewController?) -> UIViewController? {
var rootVC = rootViewController
if rootVC == nil {
rootVC = UIApplication.shared.keyWindow?.rootViewController
}
if rootVC?.presentedViewController == nil {
return rootVC
}
if let presented = rootVC?.presentedViewController {
if presented.isKind(of: UINavigationController.self) {
let navigationController = presented as! UINavigationController
return navigationController.viewControllers.last!
}
if presented.isKind(of: UITabBarController.self) {
let tabBarController = presented as! UITabBarController
return tabBarController.selectedViewController!
}
return getVisibleViewController(presented)
}
return nil
}
Old answer:
func applicationWillResignActive(application: UIApplication) {
let currentViewController = getVisibleViewController(nil)
}
func getVisibleViewController(var rootViewController: UIViewController?) -> UIViewController? {
if rootViewController == nil {
rootViewController = UIApplication.sharedApplication().keyWindow?.rootViewController
}
if rootViewController?.presentedViewController == nil {
return rootViewController
}
if let presented = rootViewController?.presentedViewController {
if presented.isKindOfClass(UINavigationController) {
let navigationController = presented as! UINavigationController
return navigationController.viewControllers.last!
}
if presented.isKindOfClass(UITabBarController) {
let tabBarController = presented as! UITabBarController
return tabBarController.selectedViewController!
}
return getVisibleViewController(presented)
}
return nil
}

We implemented it as an UIApplication extension:
import UIKit
extension UIApplication {
var visibleViewController: UIViewController? {
guard let rootViewController = keyWindow?.rootViewController else {
return nil
}
return getVisibleViewController(rootViewController)
}
private func getVisibleViewController(_ rootViewController: UIViewController) -> UIViewController? {
if let presentedViewController = rootViewController.presentedViewController {
return getVisibleViewController(presentedViewController)
}
if let navigationController = rootViewController as? UINavigationController {
return navigationController.visibleViewController
}
if let tabBarController = rootViewController as? UITabBarController {
return tabBarController.selectedViewController
}
return rootViewController
}
}

Here is an answer in Swift 4 that is very similar to the accepted answer but has a few improvements:
Iterative instead of recursive.
Goes all the way does the navigation stack.
More "swifty" syntax.
Static variable that can be put anywhere (not just in AppDelegate).
Won't crash in odd cases, i.e. when a tab bar controller has no selected view controller.
static var visibleViewController: UIViewController? {
var currentVc = UIApplication.shared.keyWindow?.rootViewController
while let presentedVc = currentVc?.presentedViewController {
if let navVc = (presentedVc as? UINavigationController)?.viewControllers.last {
currentVc = navVc
} else if let tabVc = (presentedVc as? UITabBarController)?.selectedViewController {
currentVc = tabVc
} else {
currentVc = presentedVc
}
}
return currentVc
}

The top recommendations here will work ok in many scenarios to get the 'best guess' solution but with a few minor adjustments, we can get a more complete solution that doesn't rely on your app's view hierarchy implementation.
1) Cocoa Touch's view hierarchy allows for multiple children to be present and visible at one time so we need to instead ask for the current visible view controllers (plural) and handle the results accordingly
2) UINavigationControllers and UITabBarControllers are commonly used in iOS applications, but they are not the only kind of container view controllers. UIKit also supplies the UIPageViewController, UISplitViewController, and allows you to write your own custom container view controllers.
3) We probably want to ignore popover modals and specific types of view controllers such UIAlertControllers or a custom embedded child-viewcontroller.
private func visibleViewControllers() -> [UIViewController] {
guard let root = window?.rootViewController else { return [] }
return visibleLeaves(from: root, excluding: [UIAlertController.self])
}
private func visibleLeaves(from parent: UIViewController, excluding excludedTypes: [UIViewController.Type] = []) -> [UIViewController] {
let isExcluded: (UIViewController) -> Bool = { vc in
excludedTypes.contains(where: { vc.isKind(of: $0) }) || vc.modalPresentationStyle == .popover
}
if let presented = parent.presentedViewController, !isExcluded(presented) {
return self.visibleLeaves(from: presented, excluding: excludedTypes)
}
let visibleChildren = parent.childViewControllers.filter {
$0.isViewLoaded && $0.view.window != nil
}
let visibleLeaves = visibleChildren.flatMap {
return self.visibleLeaves(from: $0, excluding: excludedTypes)
}
if visibleLeaves.count > 0 {
return visibleLeaves
} else if !isExcluded(parent) {
return [parent]
} else {
return []
}
}

This is an improved version of #ProgrammierTier's answer. If you have a navbar nested in a tabbar, you will get back UINavigationController using #ProgrammierTier's answer. Also, there is less force unwrapping. This should address the issue #Harendra-Tiwari is facing.
Swift 4.2:
func getVisibleViewController(_ rootViewController: UIViewController?) -> UIViewController? {
var rootVC = rootViewController
if rootVC == nil {
rootVC = UIApplication.shared.keyWindow?.rootViewController
}
var presented = rootVC?.presentedViewController
if rootVC?.presentedViewController == nil {
if let isTab = rootVC?.isKind(of: UITabBarController.self), let isNav = rootVC?.isKind(of: UINavigationController.self) {
if !isTab && !isNav {
return rootVC
}
presented = rootVC
} else {
return rootVC
}
}
if let presented = presented {
if presented.isKind(of: UINavigationController.self) {
if let navigationController = presented as? UINavigationController {
return navigationController.viewControllers.last!
}
}
if presented.isKind(of: UITabBarController.self) {
if let tabBarController = presented as? UITabBarController {
if let navigationController = tabBarController.selectedViewController! as? UINavigationController {
return navigationController.viewControllers.last!
} else {
return tabBarController.selectedViewController!
}
}
}
return getVisibleViewController(presented)
}
return nil
}

Here is a recursive, protocol-oriented approach in Swift. Can be extended to custom types but any kind of UIViewController subclass should work with the code below.
public protocol ViewControllerContainer {
var topMostViewController: UIViewController? { get }
}
extension UIViewController: ViewControllerContainer {
public var topMostViewController: UIViewController? {
if let presentedView = presentedViewController {
return recurseViewController(presentedView)
}
return childViewControllers.last.map(recurseViewController)
}
}
extension UITabBarController {
public override var topMostViewController: UIViewController? {
return selectedViewController.map(recurseViewController)
}
}
extension UINavigationController {
public override var topMostViewController: UIViewController? {
return viewControllers.last.map(recurseViewController)
}
}
extension UIWindow: ViewControllerContainer {
public var topMostViewController: UIViewController? {
return rootViewController.map(recurseViewController)
}
}
func recurseViewController(viewController: UIViewController) -> UIViewController {
return viewController.topMostViewController.map(recurseViewController) ?? viewController
}

If you are using IQKeyboardManager they have an extension in there
(UIViewController*)currentViewController;
so you can do
application.keyWindow?.currentViewController? // <- there you go
so add this to your pod file
pod 'IQKeyboardManager'
then pod update and you are away!
hope this helps

If your app's root view controller is a UINavigationController than you can use this:
UIViewController *currentControllerName = ((UINavigationController*)appDelegate.window.rootViewController).visibleViewController;
and if you are using UITabBarController than you can use this:
UIViewController *currentControllerName = ((UITabBarController*)appDelegate.window.rootViewController).selectedViewController;

Here's a Swift 2.3 implementation of #ProgrammierTier's answer as an extension to a UIViewController
extension UIViewController {
var visibleViewController: UIViewController? {
if presentedViewController == nil {
return self
}
if let presented = presentedViewController {
if presented.isKindOfClass(UINavigationController) {
let navigationController = presented as! UINavigationController
return navigationController.viewControllers.last
}
if presented.isKindOfClass(UITabBarController) {
let tabBarController = presented as! UITabBarController
return tabBarController.selectedViewController
}
return presented.visibleViewController
}
return nil
}
}
To get it from applicationWillResignActive
func applicationWillResignActive(application: UIApplication) {
let visibleVC = application.keyWindow?.rootViewController?.visibleViewController
}

Here's just a quick fix inspired from #krcjr89's answer. The accepted answer doesn't go all the way down the navigation. For instance, if you have a navigation controller embedded in tab bar controller, you won't get to the visible view controller but the navigation controller.
I made it an extension of UIApplication like #Christian, as this makes the most sense.
extension UIApplication {
var visibleViewController: UIViewController? {
return getVisibleViewController(nil)
}
private func getVisibleViewController(_ rootViewController: UIViewController?) -> UIViewController? {
let rootVC = rootViewController ?? UIApplication.shared.keyWindow?.rootViewController
if rootVC!.isKind(of: UINavigationController.self) {
let navigationController = rootVC as! UINavigationController
return getVisibleViewController(navigationController.viewControllers.last!)
}
if rootVC!.isKind(of: UITabBarController.self) {
let tabBarController = rootVC as! UITabBarController
return getVisibleViewController(tabBarController.selectedViewController!)
}
if let presentedVC = rootVC?.presentedViewController {
return getVisibleViewController(presentedVC)
}
return rootVC
}
}

A modified version of a previous answer using UIViewController category in ObjC:
UIViewController+VisibleViewController.h
#import <UIKit/UIKit.h>
#interface UIViewController (VisibleViewController)
- (UIViewController *)visibleViewController;
#end
UIViewController+VisibleViewController.m
#import "UIViewController+VisibleViewController.h"
#implementation UIViewController (VisibleViewController)
- (UIViewController *)visibleViewController {
if (self.presentedViewController == nil) {
return self;
}
if ([self.presentedViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController *)self.presentedViewController;
UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
return [lastViewController visibleViewController];
}
if ([self.presentedViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController *tabBarController = (UITabBarController *)self.presentedViewController;
UIViewController *selectedViewController = tabBarController.selectedViewController;
return [selectedViewController visibleViewController];
}
UIViewController *presentedViewController = (UIViewController *)self.presentedViewController;
return [presentedViewController visibleViewController];
}
#end
AppDelegate.m
#import "UIViewController+VisibleViewController.h"
- (UIViewController *) applicationVisibleViewController {
return [self.window.rootViewController visibleViewController];
}

In my case i have Tabbar controller and then Navigation controller for each Tab hope it helps someone
UIViewController *loginViewController = self.window.rootViewController;
UITabBarController *controller = loginViewController.tabBarController;
UIViewController *currentController = controller.selectedViewController.childViewControllers.lastObject;

modified from troop231
+ (UIViewController *)visibleViewController:(UIViewController *)rootViewController
{
if ([rootViewController isKindOfClass:[UINavigationController class]])
{
UINavigationController *navigationController = (UINavigationController *)rootViewController;
UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
return [self visibleViewController:lastViewController];
}
if ([rootViewController isKindOfClass:[UITabBarController class]])
{
UITabBarController *tabBarController = (UITabBarController *)rootViewController;
UIViewController *selectedViewController = tabBarController.selectedViewController;
return [self visibleViewController:selectedViewController];
}
if (rootViewController.presentedViewController != nil)
{
UIViewController *presentedViewController = (UIViewController *)rootViewController.presentedViewController;
return [self visibleViewController:presentedViewController];
}
return rootViewController;
}

Related

Top most ViewController under UIAlertController

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?
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
}
}
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.
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
}
}
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
}
}
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
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
}
}

How to get top view controller when UIImagePickerController is presented?

At some time I present UIImagePickerViewController. Once it is presented, I call my function: UIStoryboard.topViewController():
extension UIStoryboard {
class func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let svc = base as? UISplitViewController where svc.viewControllers.count == 1 {
return topViewController(svc.viewControllers[0])
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
return base
}
}
When I print result, all I get is:
0x000000014cb2aa00
{
UIKit.UIResponder = {...}
}
How to get UIImagePickerController from topViewController() function?
Not a complete answer to your case, but here's how I find the top VC in my program. You should be able to edit it for your case.
class UIHelper {
static func getCurrentViewController() -> UIViewController? {
var currentViewController: UIViewController?
if let window = UIApplication.sharedApplication().delegate?.window {
currentViewController = window!.rootViewController?.presentedViewController
}
if currentViewController == nil {
return nil
}
//Check for my version of my main tab bar VC
if let tabBarController = currentViewController as? RootTabBarController {
currentViewController = tabBarController.selectedViewController
print("Tab bar presents \(currentViewController)")
}
// Check if it's a nav VC
if let navController = currentViewController as? UINavigationController {
currentViewController = navController.viewControllers[0] as? UIViewController
print("Nav controller presents \(currentViewController)")
}
print("Current controller: \(currentViewController)")
return currentViewController
}
}

Calling alert view from another class, but one is not in hierarchy

I'm trying to call function from one class in view controller:
var rate = RateMyApp.sharedInstance
rate.showRatingAlert()
and in RateMyApp class I call alert view:
var window: AnyObject = UIApplication.sharedApplication().keyWindow!;
var vc = window.rootViewController;
vc?!.presentViewController(alert, animated: true, completion: nil)
However, the alert is not shown and there is a kind of warning:
Warning: Attempt to present <UIAlertController: 0x15fe3dcf0> on <drawtext.ViewController: 0x16002a800> whose view is not in the window hierarchy!
That happens from time to time, if one hides the app and then opens it again several times, the alert sometimes is shown.
What can cause such strange behavior?
While using UIAlertController it should be presented form the topmost ViewController in the view hierarchy.
so i suggest:
var rate = RateMyApp.sharedInstance
rate.showRatingAlert(self)
In RateMyApp class:
func showRatingAlert(sender:UIViewController){
//..... your UIAlertController here
sender.presentViewController(alert, animated: true, completion: nil)
}
On a more general note, the func presentViewController(:_) should be called only from the top-most View Controller.
You can create an extension for it like so:
extension UIViewController {
#warn_unused_result
static func topViewController(base: UIViewController? = UIApplication.sharedApplication().windows.first?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let tab = base as? UITabBarController {
let moreNavigationController = tab.moreNavigationController
if let top = moreNavigationController.topViewController where top.view.window != nil {
return topViewController(top)
} else if let selected = tab.selectedViewController {
return topViewController(selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
if let splitVC = base as? UISplitViewController {
if let lastVC = splitVC.viewControllers.last {
return topViewController(lastVC)
}
else {
return splitVC
}
}
if let lastChildVC = base?.childViewControllers.last {
return topViewController(lastChildVC)
}
return base
}
}
You can call the alert as follows:
func showRatingAlert() {
if let vc = UIViewController.topViewController() {
vc.presentViewController(alert, animated: true, completion: nil)
}
}
Seems to be solved:
var window = UIApplication.sharedApplication().keyWindow!
// var vc = window.rootViewController!!.presentingViewController
var vc = window.rootViewController;
while (vc!.presentedViewController != nil)
{
vc = vc!.presentedViewController;
}
vc?.presentViewController(alert, animated: true, completion: nil)
looks like Chetan's solution
As per my thought you just need
Get top most viewcontroller [currently presented]
Present your alert.
To Get Topmost View Controller
[This is obj C code. Please make efforts to convert in swift]
-(UIViewController*)topViewController {
return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
-(UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
if ([rootViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController* tabBarController = (UITabBarController*)rootViewController;
return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
} else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController*)rootViewController;
return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
} else if (rootViewController.presentedViewController) {
UIViewController* presentedViewController = rootViewController.presentedViewController;
return [self topViewControllerWithRootViewController:presentedViewController];
} else {
return rootViewController;
}
}
Then
UIViewController *topController = [self topViewController];
Now present your alert on topController.
Sorry i can't deliver you Swift Code. But Hope this is enough to hint.
Do it when the window is up
dispatch_async(dispatch_get_main_queue(), {
var window: AnyObject = UIApplication.sharedApplication().keyWindow!;
var vc = window.rootViewController;
vc?!.presentViewController(alert, animated: true, completion: nil)
})

Get the current view controller from the app delegate

i am new to ios. I need to know the current view controller from app delegate.. i have no idea about this and i don't knowto implement this. i am using this code toimplemnt this but it return null values.
I followed this link-
Get current view controller from the app delegate (modal is possible)
need help.
This is what I use for finding the current view controller that the user is most likely interacting with:
UIViewController+Utils.h
#import <UIKit/UIKit.h>
#interface UIViewController (Utils)
+(UIViewController*) currentViewController;
#end
UIViewController+Utils.m
#import "UIViewController+Utils.h"
#implementation UIViewController (Utils)
+(UIViewController*) findBestViewController:(UIViewController*)vc {
if (vc.presentedViewController) {
// Return presented view controller
return [UIViewController findBestViewController:vc.presentedViewController];
} else if ([vc isKindOfClass:[UISplitViewController class]]) {
// Return right hand side
UISplitViewController* svc = (UISplitViewController*) vc;
if (svc.viewControllers.count > 0)
return [UIViewController findBestViewController:svc.viewControllers.lastObject];
else
return vc;
} else if ([vc isKindOfClass:[UINavigationController class]]) {
// Return top view
UINavigationController* svc = (UINavigationController*) vc;
if (svc.viewControllers.count > 0)
return [UIViewController findBestViewController:svc.topViewController];
else
return vc;
} else if ([vc isKindOfClass:[UITabBarController class]]) {
// Return visible view
UITabBarController* svc = (UITabBarController*) vc;
if (svc.viewControllers.count > 0)
return [UIViewController findBestViewController:svc.selectedViewController];
else
return vc;
} else {
// Unknown view controller type, return last child view controller
return vc;
}
}
+(UIViewController*) currentViewController {
// Find best view controller
UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
return [UIViewController findBestViewController:viewController];
}
#end
Then whenever I need the current view controller from anywhere in the app simply use:
[UIViewController currentViewController]
Here're some class/static functions in swift that I keep in a Utility class and can help you:
// Returns the most recently presented UIViewController (visible)
class func getCurrentViewController() -> UIViewController? {
// If the root view is a navigation controller, we can just return the visible ViewController
if let navigationController = getNavigationController() {
return navigationController.visibleViewController
}
// Otherwise, we must get the root UIViewController and iterate through presented views
if let rootController = UIApplication.shared.keyWindow?.rootViewController {
var currentController: UIViewController! = rootController
// Each ViewController keeps track of the view it has presented, so we
// can move from the head to the tail, which will always be the current view
while( currentController.presentedViewController != nil ) {
currentController = currentController.presentedViewController
}
return currentController
}
return nil
}
// Returns the navigation controller if it exists
class func getNavigationController() -> UINavigationController? {
if let navigationController = UIApplication.shared.keyWindow?.rootViewController {
return navigationController as? UINavigationController
}
return nil
}
Swift version of jjv360's great answer,
(I got rid of some redundant returns, and I think Swift is more readable)
func getCurrentViewController(_ vc: UIViewController) -> UIViewController? {
if let pvc = vc.presentedViewController {
return getCurrentViewController(pvc)
}
else if let svc = vc as? UISplitViewController, svc.viewControllers.count > 0 {
return getCurrentViewController(svc.viewControllers.last!)
}
else if let nc = vc as? UINavigationController, nc.viewControllers.count > 0 {
return getCurrentViewController(nc.topViewController!)
}
else if let tbc = vc as? UITabBarController {
if let svc = tbc.selectedViewController {
return getCurrentViewController(svc)
}
}
return vc
}
From you AppDelegate,
guard let rvc = self.window?.rootViewController else {
return
}
if let vc = getCurrentViewController(rvc) {
// do your stuff here
}
This helped me to find the visible view controller. I searched for existing methods and didn't find any. So I wrote my own custom one.
-(id)getCurrentViewController
{
id WindowRootVC = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
id currentViewController = [self findTopViewController:WindowRootVC];
return currentViewController;
}
-(id)findTopViewController:(id)inController
{
/* if ur using any Customs classes, do like this.
* Here SlideNavigationController is a subclass of UINavigationController.
* And ensure you check the custom classes before native controllers , if u have any in your hierarchy.
if ([inController isKindOfClass:[SlideNavigationController class]])
{
return [self findTopViewController:[inController visibleViewController]];
}
else */
if ([inController isKindOfClass:[UITabBarController class]])
{
return [self findTopViewController:[inController selectedViewController]];
}
else if ([inController isKindOfClass:[UINavigationController class]])
{
return [self findTopViewController:[inController visibleViewController]];
}
else if ([inController isKindOfClass:[UIViewController class]])
{
return inController;
}
else
{
NSLog(#"Unhandled ViewController class : %#",inController);
return nil;
}
}
And sample use :
-(void)someMethod
{
id currentVC = [self getCurrentViewController];
if (currentVC)
{
NSLog(#"currentVC :%#",currentVC);
}
}
It depends on how you set up your UI. You can possibly get your rootViewController and move through the hierarchy if it is set up in such a way.
UIViewController *vc = self.window.rootViewController;
UIViewController* actualVC = [anyViewController.navigationController.viewControllers lastObject];
I get the root controller and then iterate through presented VC's:
UIViewController *current = [UIApplication sharedApplication].keyWindow.rootViewController;
while (current.presentedViewController) {
current = current.presentedViewController;
}
//now you can use current, for example to present an alert view controller:
[current presentViewController:alert animated:YES completion:nil];
|*| Get Visible View Controller from Navigation View Controller
let NavVccVar = UIApplication.sharedApplication().keyWindow?.rootViewController as! UINavigationController
let ShnSrnVar = NavVccVar.visibleViewController
|*| Presenting from the Visible View Controller
let NavVccVar = UIApplication.sharedApplication().keyWindow?.rootViewController as! UINavigationController
NavVccVar.visibleViewController!.presentViewController(NamVccVar, animated: true, completion: nil)
This is the best solution that I have tried out yet
+ (UIViewController*) topMostController
{
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
return topController;
}
Swift solution For iOS 13+ We have SceneDelgate and multiple windows so you need to use the following code:
private func getCurrentViewController() -> UIViewController? {
if let rootViewController = UIApplication.shared.windows.first?.rootViewController {
if let presentedViewController = rootViewController.presentedViewController {
return presentedViewController
}
return rootViewController
}
return nil
}
i am using this code-
//in AppDelegate:
#interface AppDelegate()
{
id lastViewController;
}
#implementation AppDelegate
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleCurrentViewController) name:#"CurrentViewController" object:nil];
}
-(void)handleCurrentViewController:(NSNotification *)notification
{
if([[notification userInfo] objectForKey:#"lastViewController"])
{
lastViewController = [[notification userInfo] objectForKey:#"lastViewController"];
}
}
-(void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(#"last view controller is %#", [(UIViewController *)lastViewController class]);
}
#end
//in every ViewController you want to detect
#implementation SomeViewController
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] postNotificationName:#"CurrentViewController" object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys:self, #"lastViewController", nil]];
}
I found this and it works great for me for all types of segues and view controllers. It's very simple and short too which is nice.
+(UIViewController*) getTopController{
UIViewController *topViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topViewController.presentedViewController) {
topViewController = topViewController.presentedViewController;
}
return topViewController; }

How to find topmost view controller on iOS

I've run into a couple of cases now where it would be convenient to be able to find the "topmost" view controller (the one responsible for the current view), but haven't found a way to do it.
Basically the challenge is this: Given that one is executing in a class that is not a view controller (or a view) [and does not have the address of an active view] and has not been passed the address of the topmost view controller (or, say, the address of the navigation controller), is it possible to find that view controller? (And, if so, how?)
Or, failing that, is it possible to find the topmost view?
I think you need a combination of the accepted answer and #fishstix's
+ (UIViewController*) topMostController
{
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
return topController;
}
Swift 3.0+
func topMostController() -> UIViewController? {
guard let window = UIApplication.shared.keyWindow, let rootViewController = window.rootViewController else {
return nil
}
var topController = rootViewController
while let newTopController = topController.presentedViewController {
topController = newTopController
}
return topController
}
To complete JonasG's answer (who left out tab bar controllers while traversing), here is my version of returning the currently visible view controller:
- (UIViewController*)topViewController {
return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
if ([rootViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController* tabBarController = (UITabBarController*)rootViewController;
return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
} else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController*)rootViewController;
return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
} else if (rootViewController.presentedViewController) {
UIViewController* presentedViewController = rootViewController.presentedViewController;
return [self topViewControllerWithRootViewController:presentedViewController];
} else {
return rootViewController;
}
}
iOS 4 introduced the rootViewController property on UIWindow:
[UIApplication sharedApplication].keyWindow.rootViewController;
You'll need to set it yourself after you create the view controller though.
A complete non-recursive version, taking care of different scenarios:
The view controller is presenting another view
The view controller is a UINavigationController
The view controller is a UITabBarController
Objective-C
UIViewController *topViewController = self.window.rootViewController;
while (true)
{
if (topViewController.presentedViewController) {
topViewController = topViewController.presentedViewController;
} else if ([topViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *nav = (UINavigationController *)topViewController;
topViewController = nav.topViewController;
} else if ([topViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController *tab = (UITabBarController *)topViewController;
topViewController = tab.selectedViewController;
} else {
break;
}
}
Swift 4+
extension UIWindow {
func topViewController() -> UIViewController? {
var top = self.rootViewController
while true {
if let presented = top?.presentedViewController {
top = presented
} else if let nav = top as? UINavigationController {
top = nav.visibleViewController
} else if let tab = top as? UITabBarController {
top = tab.selectedViewController
} else {
break
}
}
return top
}
}
Getting top most view controller for Swift using extensions
Code:
extension UIViewController {
#objc func topMostViewController() -> UIViewController {
// Handling Modal views
if let presentedViewController = self.presentedViewController {
return presentedViewController.topMostViewController()
}
// Handling UIViewController's added as subviews to some other views.
else {
for view in self.view.subviews
{
// Key property which most of us are unaware of / rarely use.
if let subViewController = view.next {
if subViewController is UIViewController {
let viewController = subViewController as! UIViewController
return viewController.topMostViewController()
}
}
}
return self
}
}
}
extension UITabBarController {
override func topMostViewController() -> UIViewController {
return self.selectedViewController!.topMostViewController()
}
}
extension UINavigationController {
override func topMostViewController() -> UIViewController {
return self.visibleViewController!.topMostViewController()
}
}
Usage:
UIApplication.sharedApplication().keyWindow!.rootViewController!.topMostViewController()
To complete Eric's answer (who left out popovers, navigation controllers, tabbarcontrollers, view controllers added as subviews to some other view controllers while traversing), here is my version of returning the currently visible view controller:
=====================================================================
- (UIViewController*)topViewController {
return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)viewController {
if ([viewController isKindOfClass:[UITabBarController class]]) {
UITabBarController* tabBarController = (UITabBarController*)viewController;
return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
} else if ([viewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navContObj = (UINavigationController*)viewController;
return [self topViewControllerWithRootViewController:navContObj.visibleViewController];
} else if (viewController.presentedViewController && !viewController.presentedViewController.isBeingDismissed) {
UIViewController* presentedViewController = viewController.presentedViewController;
return [self topViewControllerWithRootViewController:presentedViewController];
}
else {
for (UIView *view in [viewController.view subviews])
{
id subViewController = [view nextResponder];
if ( subViewController && [subViewController isKindOfClass:[UIViewController class]])
{
if ([(UIViewController *)subViewController presentedViewController] && ![subViewController presentedViewController].isBeingDismissed) {
return [self topViewControllerWithRootViewController:[(UIViewController *)subViewController presentedViewController]];
}
}
}
return viewController;
}
}
=====================================================================
And now all you need to do to get top most view controller is call the above method as follows:
UIViewController *topMostViewControllerObj = [self topViewController];
This answer includes childViewControllers and maintains a clean and readable implementation.
+ (UIViewController *)topViewController
{
UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
return [rootViewController topVisibleViewController];
}
- (UIViewController *)topVisibleViewController
{
if ([self isKindOfClass:[UITabBarController class]])
{
UITabBarController *tabBarController = (UITabBarController *)self;
return [tabBarController.selectedViewController topVisibleViewController];
}
else if ([self isKindOfClass:[UINavigationController class]])
{
UINavigationController *navigationController = (UINavigationController *)self;
return [navigationController.visibleViewController topVisibleViewController];
}
else if (self.presentedViewController)
{
return [self.presentedViewController topVisibleViewController];
}
else if (self.childViewControllers.count > 0)
{
return [self.childViewControllers.lastObject topVisibleViewController];
}
return self;
}
I recently got this situation in one my project, which required to displayed a notification view whatever the controller displayed was and whatever was the type (UINavigationController, classic controller or custom view controller), when network status changed.
So I juste released my code, which is quite easy and actually based on a protocol so that it is flexible with every type of container controller.
It seems to be related with the last answers, but in a much flexible way.
You can grab the code here : PPTopMostController
And got the top most controller using
UIViewController *c = [UIViewController topMostController];
For latest Swift Version:
Create a file, name it UIWindowExtension.swift and paste the following snippet:
import UIKit
public extension UIWindow {
public var visibleViewController: UIViewController? {
return UIWindow.getVisibleViewControllerFrom(self.rootViewController)
}
public static func getVisibleViewControllerFrom(_ vc: UIViewController?) -> UIViewController? {
if let nc = vc as? UINavigationController {
return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(pvc)
} else {
return vc
}
}
}
}
func getTopViewController() -> UIViewController? {
let appDelegate = UIApplication.shared.delegate
if let window = appDelegate!.window {
return window?.visibleViewController
}
return nil
}
Use it anywhere as:
if let topVC = getTopViewController() {
}
This is an improvement to Eric's answer:
UIViewController *_topMostController(UIViewController *cont) {
UIViewController *topController = cont;
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
if ([topController isKindOfClass:[UINavigationController class]]) {
UIViewController *visible = ((UINavigationController *)topController).visibleViewController;
if (visible) {
topController = visible;
}
}
return (topController != cont ? topController : nil);
}
UIViewController *topMostController() {
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
UIViewController *next = nil;
while ((next = _topMostController(topController)) != nil) {
topController = next;
}
return topController;
}
_topMostController(UIViewController *cont) is a helper function.
Now all you need to do is call topMostController() and the top most UIViewController should be returned!
Use below extension to grab current visible UIViewController. Worked for Swift 4.0 and later
Swift 4.0 and Later:
extension UIApplication {
class func topViewController(_ viewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = viewController as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let tab = viewController as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(selected)
}
}
if let presented = viewController?.presentedViewController {
return topViewController(presented)
}
return viewController
}
}
How to use?
let objViewcontroller = UIApplication.topViewController()
#implementation UIWindow (Extensions)
- (UIViewController*) topMostController
{
UIViewController *topController = [self rootViewController];
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
return topController;
}
#end
Here is my take on this. Thanks to #Stakenborg for pointing out the way to skip getting UIAlertView as the top most controller
-(UIWindow *) returnWindowWithWindowLevelNormal
{
NSArray *windows = [UIApplication sharedApplication].windows;
for(UIWindow *topWindow in windows)
{
if (topWindow.windowLevel == UIWindowLevelNormal)
return topWindow;
}
return [UIApplication sharedApplication].keyWindow;
}
-(UIViewController *) getTopMostController
{
UIWindow *topWindow = [UIApplication sharedApplication].keyWindow;
if (topWindow.windowLevel != UIWindowLevelNormal)
{
topWindow = [self returnWindowWithWindowLevelNormal];
}
UIViewController *topController = topWindow.rootViewController;
if(topController == nil)
{
topWindow = [UIApplication sharedApplication].delegate.window;
if (topWindow.windowLevel != UIWindowLevelNormal)
{
topWindow = [self returnWindowWithWindowLevelNormal];
}
topController = topWindow.rootViewController;
}
while(topController.presentedViewController)
{
topController = topController.presentedViewController;
}
if([topController isKindOfClass:[UINavigationController class]])
{
UINavigationController *nav = (UINavigationController*)topController;
topController = [nav.viewControllers lastObject];
while(topController.presentedViewController)
{
topController = topController.presentedViewController;
}
}
return topController;
}
Simple extension for UIApplication in Swift:
NOTE:
It cares about moreNavigationController within UITabBarController
extension UIApplication {
class func topViewController(baseViewController: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = baseViewController as? UINavigationController {
return topViewController(navigationController.visibleViewController)
}
if let tabBarViewController = baseViewController as? UITabBarController {
let moreNavigationController = tabBarViewController.moreNavigationController
if let topViewController = moreNavigationController.topViewController where topViewController.view.window != nil {
return topViewController(topViewController)
} else if let selectedViewController = tabBarViewController.selectedViewController {
return topViewController(selectedViewController)
}
}
if let splitViewController = baseViewController as? UISplitViewController where splitViewController.viewControllers.count == 1 {
return topViewController(splitViewController.viewControllers[0])
}
if let presentedViewController = baseViewController?.presentedViewController {
return topViewController(presentedViewController)
}
return baseViewController
}
}
Simple usage:
if let topViewController = UIApplication.topViewController() {
//do sth with top view controller
}
- (UIViewController*)topViewController {
return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
if ([rootViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController* tabBarController = (UITabBarController*)rootViewController;
return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
} else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController* navigationController = (UINavigationController*)rootViewController;
return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
} else if (rootViewController.presentedViewController) {
UIViewController* presentedViewController = rootViewController.presentedViewController;
return [self topViewControllerWithRootViewController:presentedViewController];
} else {
return rootViewController;
}
}
Swift 4.2 Extension
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
}
}
Use it from anywhere like,
UIApplication.topViewController()?.present(yourController, animated: true, completion: nil)
or like,
UIApplication.topViewController()?
.navigationController?
.popToViewController(yourController,
animated: true)
Fit to any classes like UINavigationController, UITabBarController
Enjoy!
A concise yet comprehensive solution in Swift 4.2, takes into account UINavigationControllers, UITabBarControllers, presented and child view controllers:
extension UIViewController {
func topmostViewController() -> UIViewController {
if let navigationVC = self as? UINavigationController,
let topVC = navigationVC.topViewController {
return topVC.topmostViewController()
}
if let tabBarVC = self as? UITabBarController,
let selectedVC = tabBarVC.selectedViewController {
return selectedVC.topmostViewController()
}
if let presentedVC = presentedViewController {
return presentedVC.topmostViewController()
}
if let childVC = children.last {
return childVC.topmostViewController()
}
return self
}
}
extension UIApplication {
func topmostViewController() -> UIViewController? {
return keyWindow?.rootViewController?.topmostViewController()
}
}
Usage:
let viewController = UIApplication.shared.topmostViewController()
Yet another Swift solution
func topController() -> UIViewController? {
// recursive follow
func follow(from:UIViewController?) -> UIViewController? {
if let to = (from as? UITabBarController)?.selectedViewController {
return follow(to)
} else if let to = (from as? UINavigationController)?.visibleViewController {
return follow(to)
} else if let to = from?.presentedViewController {
return follow(to)
}
return from
}
let root = UIApplication.sharedApplication().keyWindow?.rootViewController
return follow(root)
}
Here is what worked for me.
I found that sometimes the controller was nil on the key window, as the keyWindow is some OS thing like an alert, etc.
+ (UIViewController*)topMostController
{
UIWindow *topWndow = [UIApplication sharedApplication].keyWindow;
UIViewController *topController = topWndow.rootViewController;
if (topController == nil)
{
// The windows in the array are ordered from back to front by window level; thus,
// the last window in the array is on top of all other app windows.
for (UIWindow *aWndow in [[UIApplication sharedApplication].windows reverseObjectEnumerator])
{
topController = aWndow.rootViewController;
if (topController)
break;
}
}
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
return topController;
}
Expanding on #Eric's answer, you need to be careful that the keyWindow is actually the window you want. If you are trying to utilize this method after tapping something in an alert view for example, the keyWindow will actually be the alert's window, and that will cause problems for you no doubt. This happened to me in the wild when handling deep links via an alert and caused SIGABRTs with NO STACK TRACE. Total bitch to debug.
Here's the code I'm using now:
- (UIViewController *)getTopMostViewController {
UIWindow *topWindow = [UIApplication sharedApplication].keyWindow;
if (topWindow.windowLevel != UIWindowLevelNormal) {
NSArray *windows = [UIApplication sharedApplication].windows;
for(topWindow in windows)
{
if (topWindow.windowLevel == UIWindowLevelNormal)
break;
}
}
UIViewController *topViewController = topWindow.rootViewController;
while (topViewController.presentedViewController) {
topViewController = topViewController.presentedViewController;
}
return topViewController;
}
Feel free to mix this with whatever flavor of retrieving the top view controller you like from the other answers on this question.
Alternative Swift solution:
static func topMostController() -> UIViewController {
var topController = UIApplication.sharedApplication().keyWindow?.rootViewController
while (topController?.presentedViewController != nil) {
topController = topController?.presentedViewController
}
return topController!
}
This solution is the most complete. It takes in consideration:
UINavigationController
UIPageViewController
UITabBarController
And the topmost presented view controller from the top view controller
The example is in Swift 3.
There are 3 overloads
//Get the topmost view controller for the current application.
public func MGGetTopMostViewController() -> UIViewController? {
if let currentWindow:UIWindow = UIApplication.shared.keyWindow {
return MGGetTopMostViewController(fromWindow: currentWindow)
}
return nil
}
//Gets the topmost view controller from a specific window.
public func MGGetTopMostViewController(fromWindow window:UIWindow) -> UIViewController? {
if let rootViewController:UIViewController = window.rootViewController
{
return MGGetTopMostViewController(fromViewController: rootViewController)
}
return nil
}
//Gets the topmost view controller starting from a specific UIViewController
//Pass the rootViewController into this to get the apps top most view controller
public func MGGetTopMostViewController(fromViewController viewController:UIViewController) -> UIViewController {
//UINavigationController
if let navigationViewController:UINavigationController = viewController as? UINavigationController {
let viewControllers:[UIViewController] = navigationViewController.viewControllers
if navigationViewController.viewControllers.count >= 1 {
return MGGetTopMostViewController(fromViewController: viewControllers[viewControllers.count - 1])
}
}
//UIPageViewController
if let pageViewController:UIPageViewController = viewController as? UIPageViewController {
if let viewControllers:[UIViewController] = pageViewController.viewControllers {
if viewControllers.count >= 1 {
return MGGetTopMostViewController(fromViewController: viewControllers[0])
}
}
}
//UITabViewController
if let tabBarController:UITabBarController = viewController as? UITabBarController {
if let selectedViewController:UIViewController = tabBarController.selectedViewController {
return MGGetTopMostViewController(fromViewController: selectedViewController)
}
}
//Lastly, Attempt to get the topmost presented view controller
var presentedViewController:UIViewController! = viewController.presentedViewController
var nextPresentedViewController:UIViewController! = presentedViewController?.presentedViewController
//If there is a presented view controller, get the top most prensentedViewController and return it.
if presentedViewController != nil {
while nextPresentedViewController != nil {
//Set the presented view controller as the next one.
presentedViewController = nextPresentedViewController
//Attempt to get the next presented view controller
nextPresentedViewController = presentedViewController.presentedViewController
}
return presentedViewController
}
//If there is no topmost presented view controller, return the view controller itself.
return viewController
}
A lot of these answers are incomplete. Although this is in Objective-C, this is the best compilation of all of them that I could put together for right now, as a non-recursive block:
Link to Gist, in case it gets revised: https://gist.github.com/benguild/0d149bb3caaabea2dac3d2dca58c0816
Code for reference/comparison:
UIViewController *(^topmostViewControllerForFrontmostNormalLevelWindow)(void) = ^UIViewController *{
// NOTE: Adapted from various stray answers here:
// https://stackoverflow.com/questions/6131205/iphone-how-to-find-topmost-view-controller/20515681
UIViewController *viewController;
for (UIWindow *window in UIApplication.sharedApplication.windows.reverseObjectEnumerator.allObjects) {
if (window.windowLevel == UIWindowLevelNormal) {
viewController = window.rootViewController;
break;
}
}
while (viewController != nil) {
if ([viewController isKindOfClass:[UITabBarController class]]) {
viewController = ((UITabBarController *)viewController).selectedViewController;
} else if ([viewController isKindOfClass:[UINavigationController class]]) {
viewController = ((UINavigationController *)viewController).visibleViewController;
} else if (viewController.presentedViewController != nil && !viewController.presentedViewController.isBeingDismissed) {
viewController = viewController.presentedViewController;
} else if (viewController.childViewControllers.count > 0) {
viewController = viewController.childViewControllers.lastObject;
} else {
BOOL repeat = NO;
for (UIView *view in viewController.view.subviews.reverseObjectEnumerator.allObjects) {
if ([view.nextResponder isKindOfClass:[UIViewController class]]) {
viewController = (UIViewController *)view.nextResponder;
repeat = YES;
break;
}
}
if (!repeat) {
break;
}
}
}
return viewController;
};
Great solution in Swift, implement in AppDelegate
func getTopViewController()->UIViewController{
return topViewControllerWithRootViewController(UIApplication.sharedApplication().keyWindow!.rootViewController!)
}
func topViewControllerWithRootViewController(rootViewController:UIViewController)->UIViewController{
if rootViewController is UITabBarController{
let tabBarController = rootViewController as! UITabBarController
return topViewControllerWithRootViewController(tabBarController.selectedViewController!)
}
if rootViewController is UINavigationController{
let navBarController = rootViewController as! UINavigationController
return topViewControllerWithRootViewController(navBarController.visibleViewController)
}
if let presentedViewController = rootViewController.presentedViewController {
return topViewControllerWithRootViewController(presentedViewController)
}
return rootViewController
}
I know its very late and might be redundant. But following is the snippet I came up with which is working for me :
static func topViewController() -> UIViewController? {
return topViewController(vc: UIApplication.shared.keyWindow?.rootViewController)
}
private static func topViewController(vc:UIViewController?) -> UIViewController? {
if let rootVC = vc {
guard let presentedVC = rootVC.presentedViewController else {
return rootVC
}
if let presentedNavVC = presentedVC as? UINavigationController {
let lastVC = presentedNavVC.viewControllers.last
return topViewController(vc: lastVC)
}
return topViewController(vc: presentedVC)
}
return nil
}
Swift:
extension UIWindow {
func visibleViewController() -> UIViewController? {
if let rootViewController: UIViewController = self.rootViewController {
return UIWindow.getVisibleViewControllerFrom(rootViewController)
}
return nil
}
class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
if vc.isKindOfClass(UINavigationController.self) {
let navigationController = vc as UINavigationController
return UIWindow.getVisibleViewControllerFrom( navigationController.visibleViewController)
} else if vc.isKindOfClass(UITabBarController.self) {
let tabBarController = vc as UITabBarController
return UIWindow.getVisibleViewControllerFrom(tabBarController.selectedViewController!)
} else {
if let presentedViewController = vc.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
} else {
return vc;
}
}
}
Usage:
if let topController = window.visibleViewController() {
println(topController)
}
I think most of the answers have completely ignored UINavigationViewController, so I handled this use case with following implementation.
+ (UIViewController *)topMostController {
UIViewController * topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController || [topController isMemberOfClass:[UINavigationController class]]) {
if([topController isMemberOfClass:[UINavigationController class]]) {
topController = [topController childViewControllers].lastObject;
} else {
topController = topController.presentedViewController;
}
}
return topController;
}
This works great for finding the top viewController 1 from any root view controlle
+ (UIViewController *)topViewControllerFor:(UIViewController *)viewController
{
if(!viewController.presentedViewController)
return viewController;
return [MF5AppDelegate topViewControllerFor:viewController.presentedViewController];
}
/* View Controller for Visible View */
AppDelegate *app = [UIApplication sharedApplication].delegate;
UIViewController *visibleViewController = [AppDelegate topViewControllerFor:app.window.rootViewController];
Not sure if this will help what you're trying to accomplish by finding the topmost view controller, but I was trying to present a new view controller, but if my root view controller already had a modal dialog, it would be blocked, so I would cycle to the top of all modal view controllers using this code:
UIViewController* parentController =[UIApplication sharedApplication].keyWindow.rootViewController;
while( parentController.presentedViewController &&
parentController != parentController.presentedViewController )
{
parentController = parentController.presentedViewController;
}
Another solution relies on the responder chain, which may or may not work depending on what the first responder is:
Get the first responder.
Get the UIViewController associated with that first responder.
Example pseudo code:
+ (UIViewController *)currentViewController {
UIView *firstResponder = [self firstResponder]; // from the first link above, but not guaranteed to return a UIView, so this should be handled more appropriately.
UIViewController *viewController = [firstResponder viewController]; // from the second link above
return viewController;
}

Resources