UIViewController! Does not conform to protocol "LogicValue" - ios

I am trying to convert the following objective-c code to swift:
- (UIViewController *)currentViewController
{
UIViewController *viewController = self.rootViewController;
while (viewController.presentedViewController) {
viewController = viewController.presentedViewController;
}
return viewController;
}
#ifdef __IPHONE_7_0
- (UIViewController *)viewControllerForStatusBarStyle
{
UIViewController *currentViewController = [self currentViewController];
while ([currentViewController childViewControllerForStatusBarStyle]) {
currentViewController = [currentViewController childViewControllerForStatusBarStyle];
}
return currentViewController;
}
- (UIViewController *)viewControllerForStatusBarHidden
{
UIViewController *currentViewController = [self currentViewController];
while ([currentViewController childViewControllerForStatusBarHidden]) {
currentViewController = [currentViewController childViewControllerForStatusBarHidden];
}
return currentViewController;
}
#endif
//SWIFT
func currentViewController() -> UIViewController {
var viewController = self.rootViewController
while (viewController.presentedViewController) {
viewController = viewController.presentedViewController
}
return viewController
}
func viewControllerForStatusBarStyle() -> UIViewController {
var cViewController = currentViewController()
while (cViewController.childViewControllerForStatusBarStyle) {
cViewController = cViewController.childViewControllerForStatusBarStyle
}
return cViewController
}
func viewControllerForStatusBarHidden() -> UIViewController {
var cViewController = currentViewController()
while (cViewController.childViewControllerForStatusBarHidden) {
cViewController = cVC.childViewControllerForStatusBarHidden
}
return cViewController
}
But I get some errors saying that UIViewController! Does not conform to protocol "LogicValue" at the following line " while (cViewController.childViewControllerForStatusBarStyle)"
How can I fix this issue?

childViewControllerForStatusBarStyle() is a method not a property
while (cViewController.childViewControllerForStatusBarStyle()) {
cViewController = cViewController.childViewControllerForStatusBarStyle()
}

Related

How to get current controller from Appdelegate.m

I'm trying to hide a UIAlertView from the chat screen and shows on all other pages when the user receives a message.
Any idea how i can find the current controller from appDelegate ?
I'm a noob with objective C still learning.
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
if (isOnInstantMessagingScreen) {
//Show nothing
}
else {
UIApplicationState state = [application applicationState];
if (state == UIApplicationStateActive) {
NSString *cancelTitle = #"Close";
NSString *showTitle = #"View";
NSString *message = [[userInfo valueForKey:#"aps"] valueForKey:#"alert"];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"New Message!"
message:message
delegate:self
cancelButtonTitle:cancelTitle
otherButtonTitles:showTitle, nil];
[alertView show];
}
}
I have found this:
- (UIViewController *)topViewController{
return [self topViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController *)topViewController:(UIViewController *)rootViewController
{
if ([rootViewController isKindOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController *)rootViewController;
return [self topViewController:[navigationController.viewControllers lastObject]];
}
if ([rootViewController isKindOfClass:[UITabBarController class]]) {
UITabBarController *tabController = (UITabBarController *)rootViewController;
return [self topViewController:tabController.selectedViewController];
}
if (rootViewController.presentedViewController) {
return [self topViewController:rootViewController];
}
return rootViewController;
}
And my messages controller = MessageViewController
How can i call this code in?
If you're not using a navigation controller than you can get it by:
id myCurrentController = self.window.rootViewController; //you can cast it
If you're view controller is hosted under a navigation controller than you need an extra step.
id navigationController = self.window.rootViewController;
UINavigationController *nav = (UINavigationController*)navigationController;
id myController = nav.topViewController
Solving the same issue, having my controller nested in both UITabBarController and UINavigationController I made this (generic) extension:
import Foundation
import UIKit
extension AppDelegate {
func findController<T: UIViewController>(_ type: T.Type) -> T? {
let controller = window?.rootViewController
return findController(type: type, controller)
}
fileprivate func findController<T: UIViewController>(type: T.Type, _ controller: UIViewController?) -> T? {
guard controller != nil else {
return nil
}
return controller as? T ??
findController(type: type, nestedIn: controller as? UINavigationController) ??
findController(type: type, nestedIn: controller as? UITabBarController)
}
fileprivate func findController<T: UIViewController>(type: T.Type, nestedIn controller: UINavigationController?) -> T? {
return findController(type: type, controller?.topViewController)
}
fileprivate func findController<T: UIViewController>(type: T.Type, nestedIn controller: UITabBarController?) -> T? {
return findController(type: type, controller?.childViewControllers[controller!.selectedIndex])
}
}
Use like this:
if let controller = findController(YouViewController.self) {
controller.whateverYouNeed()
}

UITraitCollection - swift to objective-C

I'm trying to convert swift to objective-C. Here is my code below:
Swift
import UIKit
protocol TraitCollectionOverridable {
func preferredTraitCollection() -> UITraitCollection?
}
class CustomNavigationController: UINavigationController {
override func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection? {
guard let conformingViewController = childViewController as? TraitCollectionOverridable else {
return super.overrideTraitCollectionForChildViewController(childViewController)
}
return conformingViewController.preferredTraitCollection()
}
}
Objective-C
header file
#protocol TraitCollectionOverridable <NSObject>
- (UITraitCollection *) preferredTraitCollection;
#end
#interface CustomNavigationController : UINavigationController
#property (nonatomic, weak) id <TraitCollectionOverridable> traitsDelegate;
#end
.m file
#implementation CustomNavigationController
- (void)viewDidLoad {
[super viewDidLoad];
[self.traitsDelegate preferredTraitCollection];
// Do any additional setup after loading the view.
}
- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController
{
if (CGRectGetWidth(self.view.bounds) < CGRectGetHeight(self.view.bounds)) {
return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassCompact];
} else {
return [UITraitCollection traitCollectionWithHorizontalSizeClass:UIUserInterfaceSizeClassRegular];
}
}
#end
My guess is overrideTraitCollectionForChildViewController is not converted properly. Any help on that would be great.
My guess is overrideTraitCollectionForChildViewController is not converted properly.
The Obj-C & Swift versions of overrideTraitCollectionForChildViewController aren't equivalent. Here is the corrected code:
Obj-C
- (UITraitCollection *)overrideTraitCollectionForChildViewController:(UIViewController *)childViewController
{
if( [childViewController conformsToProtocol:#protocol(TraitCollectionOverridable)] ) {
return [(NSObject<TraitCollectionOverridable>*)childViewController preferredTraitCollection];
} else {
return [super overrideTraitCollectionForChildViewController:childViewController];
}
}
Swift
override func overrideTraitCollectionForChildViewController(childViewController: UIViewController) -> UITraitCollection? {
guard let conformingViewController = childViewController as? TraitCollectionOverridable else {
return super.overrideTraitCollectionForChildViewController(childViewController)
}
return conformingViewController.preferredTraitCollection()
}

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 get visible viewController from app delegate when using storyboard?

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;
}

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