I have some viewControllers
, and I don\'t use NavigationController
.
How can I get visible view controller in app delegate methods (e.g. appli
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];
}
If you are using IQKeyboardManager they have an extension in there
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
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
}
}
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) UINavigationController
s and UITabBarController
s 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 UIAlertController
s 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 []
}
}
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'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
}