如何在iOS上找到最顶层的视图控制器

285

我遇到了几种情况,需要找到“最上层”的视图控制器(负责当前视图的视图控制器),但没有找到方法。

基本上,这个挑战是这样的:假设一个类不是视图控制器(或视图)[并且没有活动视图的地址],也没有传入最上面视图控制器的地址(或者比如导航控制器的地址),那么是否可能找到该视图控制器呢?(如果可以,怎么实现?)

或者,如果找不到它,是否可能找到最上层的视图?


那么你的意思是说这是不可能的。 - Hot Licks
@Daniel 不,我的意思是你的代码似乎需要重新设计,因为你很少需要知道这个。此外,“最顶层”的概念只在特定情况下有效,即使在那种情况下也不总是有效。 - Dave DeLong
@Daniel,我误读了您的问题。 尝试回答此问题时有很多情况需要考虑。这取决于您的视图控制器流程。 @Wilbur的答案应该是追踪它的好起点。 - Deepak Danduprolu
4
@Daniel:添加第二个UIWindow 对于警报视图类似的覆盖效果很有效。 - Wilbur Vandrsmith
是的,UIWindow似乎是实现警报的方法。这并不能解决找出最上层视图控制器(想要将其放在顶部的位置)的另一个问题,但我想这些问题需要分别处理。 - Hot Licks
显示剩余2条评论
42个回答

0

不确定这是否有助于您通过查找最顶层的视图控制器来完成所需的任务,但我正在尝试呈现一个新的视图控制器。但是,如果我的根视图控制器已经有了模态对话框,则会被阻止,因此我将使用以下代码循环到所有模态视图控制器的顶部:

UIViewController* parentController =[UIApplication sharedApplication].keyWindow.rootViewController;

while( parentController.presentedViewController &&
       parentController != parentController.presentedViewController )
{
    parentController = parentController.presentedViewController;
}

0

这对于从任何根视图控制器1查找顶级视图控制器非常有效

+ (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]; 

0

之前的答案似乎没有处理 rootController 是 UITabBarController 或 UINavigationController 的情况。

这里是一个在 Swift 中适用于这些情况的函数:

func getCurrentView() -> UIViewController?
{
    if let window = UIApplication.sharedApplication().keyWindow, var currentView: UIViewController = window.rootViewController
    {
        while (currentView.presentedViewController != nil)
        {
            if let presented = currentView.presentedViewController
            {
                currentView = presented
            }
        }

        if currentView is UITabBarController
        {
            if let visible = (currentView as! UITabBarController).selectedViewController
            {
                currentView = visible;
            }
        }

        if currentView is UINavigationController
        {
            if let visible = (currentView as! UINavigationController).visibleViewController
            {
                currentView = visible;
            }
        }

        return currentView
    }

    return nil
}

0

我认为Rajesh的解决方案几乎完美,但我认为从上到下遍历子视图更好,我改成了以下方式:

+ (UIViewController *)topViewController:(UIViewController *)viewController{

    if (viewController.presentedViewController)
    {

            UIViewController *presentedViewController = viewController.presentedViewController;
            return [self topViewController:presentedViewController];
     } 
     else if ([viewController isKindOfClass:[UITabBarController class]])
     {

            UITabBarController *tabBarController = (UITabBarController *)viewController;
            return [self topViewController:tabBarController.selectedViewController];
    }

         else if ([viewController isKindOfClass:[UINavigationController class]])
    {   

            UINavigationController *navController = (UINavigationController *)viewController;

            return [self topViewController:navController.visibleViewController];
    }

    // Handling UIViewController's added as subviews to some other views.
    else {

        NSInteger subCount = [viewController.view subviews].count - 1;

        for (NSInteger index = subCount; index >=0 ; --index)
        {

            UIView *view = [[viewController.view subviews] objectAtIndex:index];

            id subViewController = [view nextResponder];    // Key property which most of us are unaware of / rarely use.

            if ( subViewController && [subViewController isKindOfClass:[UIViewController class]])
            {
                return [self topViewController:subViewController];
            }
        }
        return viewController;
    }
}

0

我认为这里可能有一件事被忽视了。也许更好的方法是将父视图控制器传递到使用视图控制器的函数中。如果您在视图层次结构中四处寻找顶部视图控制器,那么可能会违反模型层和UI层之间的分离,并且是一种代码异味。只是指出这一点,我也曾经这样做,然后意识到更简单的方法就是通过将模型操作返回到我具有对视图控制器的引用的UI层来将其传递给函数。


0
为了避免复杂性,我通过在委托中创建一个视图控制器并在每个viewDidLoad方法中将其设置为self来跟踪当前的viewController。这样,每当您加载新视图时,委托中保存的ViewController将对应于该视图的viewController。这可能很丑陋,但它非常有效,而且不需要导航控制器或任何无用的东西。

基本上这个挑战是:假设正在执行的类不是视图控制器(或视图),并且没有活动视图的地址。 - Hot Licks
将其存储在委托中并在每个类中创建一个变量“var appDelegate = UIApplication.sharedApplication()。delegate as AppDelegate”可访问委托中保存当前视图控制器的字段,该字段在viewDidLoad中设置为viewController,使用与之前提到的相同方式定义的变量appDelegate - J_Tuck

0

另一种解决方案依赖于响应链,这取决于第一响应者是什么,可能有效也可能无效:

  1. 获取第一响应者
  2. 获取与该第一响应者相关联的UIViewController

示例伪代码:

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

0

我的问题有点不同,我在我的应用程序中使用SWRevealViewController。我使用了钟雨辰的答案,但它总是将topViewController返回为SWRevealViewController。对于那些正在使用SWRevealViewController或其他pod来开发侧边菜单的人来说,这是我对钟雨辰答案的扩展:

extension UIApplication {
class func topViewController() -> UIViewController? {
    var topVC = shared.keyWindow!.rootViewController
    while true {
        if let presented = topVC?.presentedViewController {
            topVC = presented
        } else if let nav = topVC as? UINavigationController {
            topVC = nav.visibleViewController
        } else if let tab = topVC as? UITabBarController {
            topVC = tab.selectedViewController
        }else if let swRVC = topVC as? SWRevealViewController {
            topVC = swRVC.frontViewController
        } else {
            break
        }
    }
    return topVC
}
}

-1

Swift 5

试一下这个

let topVisibleVC = UIApplication.shared.keyWindow?.rootViewController?.visibleViewController

-1

你可以使用以下方法找到最顶层的视图控制器

NSArray *arrViewControllers=[[self navigationController] viewControllers];
UIViewController *topMostViewController=(UIViewController *)[arrViewControllers objectAtIndex:[arrViewControllers count]-1];

1
除此之外,如果您实际阅读了问题,您会发现self没有navigationController属性。 - Hot Licks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接