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()
}
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)
})
Please help me. I have an app that has a passcode lock feature, this gets activated everytime the user opens the app. If the user goes to home ( app goes to background) and then go back to the app ( foreground) I want to display the lock screen again ( or use other viewcontroller ). How am I going to do this?
I know about the WillEnterForeground in appDelegate, I just can't figure out how am I going to tell my app to present the lock screen instead of the screen it had prior to going to background.
This app is very similar to the photo vault application. Photos are guarded with lock screen and user gets asked the passcode everytime he opens the app
Thank you so much in advance
There are a couple of libraries available
https://github.com/antiraum/THPinViewController
https://github.com/D-32/DMPasscode
https://github.com/tiny2n/JKLLockScreenViewController
If you wish to do it yourself or in middle of implenting it, get the top most controller in willEnterForground method, present your lock screen controller on that controller, on successful pin checking dismiss your lock screen controller, and if you need to notify others, push a notificiation
This is how you can get the top most controller
- (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
func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController! {
if rootViewController.isKindOfClass(UITabBarController) {
let tabbarController = rootViewController as! UITabBarController
return self.topViewControllerWithRootViewController(tabbarController.selectedViewController)
}else if (rootViewController.isKindOfClass(UINavigationController)) {
let navigationController = rootViewController as! UINavigationController
return self.topViewControllerWithRootViewController(navigationController.visibleViewController)
}else if ((rootViewController.presentedViewController) != nil){
let controller = rootViewController.presentedViewController
return self.topViewControllerWithRootViewController(controller)
}else {
return rootViewController
}
}
Usage
let topController = self.topViewControllerWithRootViewController(UIApplication.sharedApplication().delegate?.window??.rootViewController)
Modifiy and make an extension or use it as you wish
Addition to the answer
swift 3.0:
func topViewControllerWithRootViewController(rootViewController: UIViewController!) -> UIViewController! {
if rootViewController is UITabBarController {
let tabbarController = rootViewController as! UITabBarController
return self.topViewControllerWithRootViewController(rootViewController: tabbarController.selectedViewController)
} else if rootViewController is UINavigationController {
let navigationController = rootViewController as! UINavigationController
return self.topViewControllerWithRootViewController(rootViewController: navigationController.visibleViewController)
} else if ((rootViewController.presentedViewController) != nil){
let controller = rootViewController.presentedViewController
return self.topViewControllerWithRootViewController(rootViewController: controller)
} else {
return rootViewController
}
}
Usage:
let topController = self.topViewControllerWithRootViewController(rootViewController: UIApplication.shared.delegate?.window??.rootViewController)
Addition to the answer
Swift 4 (with extension)
In your app delegate -
// Show the pin view controller when app becomes active
func applicationDidBecomeActive(_ application: UIApplication) {
if shouldAskForPin {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = storyboard.instantiateViewController(withIdentifier: "PinViewController")
let topVC = self.window?.rootViewController?.topViewController()
topVC!.present(loginVC, animated: true, completion: nil)
}
}
If the app goes to background while pin view is showing, when the app becomes active again, the pin view controller will be added again on top of it and so on. So remove it when resigning. Make sure your restoration identifier is set in the storyboard (or through code) for the PIN View Controller.
func applicationWillResignActive(_ application: UIApplication) {
if shouldAskForPin {
let topVC = self.window?.rootViewController?.topViewController()
if topVC!.restorationIdentifier == "PinViewController" {
topVC!.dismiss(animated: false, completion: nil)
}
}
}
And finally, the extension that makes it work based on the answers above. Usually good to put in a separate file e.g. UIViewController+Extension.swift
import UIKit
extension UIViewController {
func topViewController() -> UIViewController! {
if self.isKind(of: UITabBarController.self) {
let tabbarController = self as! UITabBarController
return tabbarController.selectedViewController!.topViewController()
} else if (self.isKind(of: UINavigationController.self)) {
let navigationController = self as! UINavigationController
return navigationController.visibleViewController!.topViewController()
} else if ((self.presentedViewController) != nil){
let controller = self.presentedViewController
return controller!.topViewController()
} else {
return self
}
}
}
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;
}
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;
}