ChatGPT解决这个技术问题 Extra ChatGPT

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

我现在遇到了几种情况,可以很方便地找到“最顶层”的视图控制器(负责当前视图的控制器),但还没有找到方法。

基本上挑战是这样的:假设一个在不是视图控制器(或视图)的类中执行[并且没有活动视图的地址]并且没有传递最顶层视图控制器的地址(或者说,导航控制器的地址),是否可以找到该视图控制器? (如果是这样,怎么做?)

或者,如果做不到这一点,是否有可能找到最顶层的视图?

所以你说这是不可能的。
@Daniel 不,我是说您的代码似乎可以重新设计,因为您很少需要知道这一点。此外,“最上层”的概念仅在某些情况下有效,即使如此也并非总是如此。
@Daniel 我误读了你的问题。有很多如果和但试图回答这个问题。这取决于您的视图控制器流程。 @Wilbur 的回答应该是一个很好的起点来追踪它。
好吧,让我们将其简化为特定情况。如果我想写一个 UIAlertView 的克隆,我会怎么做?请注意,它可以正常运行,而无需将任何可寻址性传递给其他控制器或视图。
@Daniel:添加第二个 UIWindow 非常适合类似于警报视图的叠加层。

v
vrwim

我认为您需要将接受的答案和@fishstix 结合起来

+ (UIViewController*) topMostController
{
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;

    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }

    return topController;
}

斯威夫特 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
}

此外,您可以检查 UINavigationController 并要求其 topViewController,甚至检查 UITabBarController 并要求 selectedViewController。这将为您提供当前对用户可见的视图控制器。
这是一个不完整的解决方案,因为它只遍历模态呈现的视图控制器的层次结构,而不是 childViewControllers 的层次结构(如 UINavigationController、UITabBarController 等所使用的)。
这是抽象出恢复到当前应用程序状态的模态视图控制器的呈现的好方法,在我的例子中,它是应用程序超时后的密码重新输入屏幕。谢谢!
@algal:不是真的:UITabBarController、UINavigationController 已经是层次结构中最顶层的视图控制器。根据您想对“最顶层控制器”执行的操作,您可能根本不想遍历它们并摆弄它们的内容。在我的例子中,它是在一切之上呈现一个模态控制器,为此我需要获取 UINaviationController 或 UITabBarController,而不是它们的内容!
Rick77,如果这是真的,那么您在此处埋藏的一点评论会使其他答案中的大量复杂修改变得不必要。由于根本没有其他人提到这一点,我觉得我必须要求你确认这是真的。如果是的话,它是如此重要,以至于它应该成为一个自己的答案。因为大多数其他答案都会试图解决这个问题。你会拯救生命的!
C
Community

为了完成 JonasG 的 answer(在遍历时省略了标签栏控制器),这是我返回当前可见视图控制器的版本:

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

很好,是的,我忘记了 TabBar 控制器:P
不包括 childViewControllers
看看我下面的answer,它通过处理@kleo 遗漏的情况来改进上述答案,例如弹出框、在遍历时将视图控制器作为子视图添加到其他一些视图控制器
如果您使用 return [self topViewControllerWithRootViewController:navigationController.visibleViewController];,则 visibleViewController 本身会返回呈现的视图控制器(如果有的话),即使它是 UIAlertController。对于需要避免使用 ui 警报控制器的人,请使用 topViewController 而不是 visibleViewController
只是为了增加我的 50 美分 - 我正在努力让它在我的加载 webView 的视图控制器中工作。我无法让它工作的原因是因为视图还没有准备好(没有完成加载)因此它是不可见的。这导致了获取 topViewContoller 失败的情况,因为 UINavigationController 试图获取可见的 ViewController 而还没有可见的 ViewController。因此,如果有人遇到此问题,请确保您的视图完成加载,然后再调用上述 topViewController 方法。
W
Wilbur Vandrsmith

iOS 4 在 UIWindow 上引入了 rootViewController 属性:

[UIApplication sharedApplication].keyWindow.rootViewController;

不过,您需要在创建视图控制器后自己设置它。


威尔伯,这将给你的操作所要求的相反。 rootViewController 是基本视图控制器,而不是最顶层。
m4rkk:“最上面”取决于您从哪个方向看。新控制器是添加到顶部(类似堆栈)还是底部(类似树)?在任何情况下,OP 都提到导航控制器位于顶部,这意味着向下增长的视图。
单词“top”用于视图控制器,即在顶部可视化(如 -[UINavigationController topViewController])。然后是单词“root”,它是树的根(如-[UIWindow rootViewController].
@ImpurestClub 我无法在 documentation 中找到,Xcode 似乎也没有找到它。
@adib 不,它属于 UINavigationController
Y
Yuchen

一个完整的非递归版本,照顾不同的场景:

视图控制器正在呈现另一个视图

视图控制器是一个 UINavigationController

视图控制器是一个 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;
     }
 }

斯威夫特 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
    }
}

我将它命名为 visibleViewController 以明确它的作用。
谢谢!在用户使用后台或前台的应用程序点击通知后,我整天都在努力寻找一种呈现 ViewController 的方法!
显示警报时如何避免uialertcontroller
Z
Zoltán Matók

使用扩展获取 Swift 的顶级视图控制器

代码:

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()
    }
}

用法:

UIApplication.sharedApplication().keyWindow!.rootViewController!.topMostViewController()

非常好 - 非常感谢您提供此解决方案。需要子视图的技巧!再次,非常感谢,你拯救了我的一天。
H
Hardik Thakkar

为了完成 Eric 的 answer(在遍历时省略了弹出框、导航控制器、标签栏控制器、作为子视图添加到其他视图控制器的视图控制器),这是我返回当前可见视图控制器的版本:

==================================================== ====================

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

==================================================== ====================

现在你需要做的就是调用上面的方法来获得最顶层的视图控制器,如下所示:

UIViewController *topMostViewControllerObj = [self topViewController];

也缺少 SplitViewController?
A
Awesome-o

此答案包括 childViewControllers 并保持干净易读的实现。

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

更新了一些代码,通过最小化并再次恢复它来显示它是什么控制器。 nik-kov-ios-developer.blogspot.ru/2016/12/…
嘿,来吧,你的“topVisibleViewController”在哪里?
i
ipodishima

我最近在我的一个项目中遇到了这种情况,当网络状态发生变化时,无论控制器显示的是什么类型(UINavigationController、经典控制器或自定义视图控制器),它都需要显示一个通知视图。

所以我刚刚发布了我的代码,它非常简单并且实际上基于一个协议,因此它对每种类型的容器控制器都很灵活。它似乎与最后的答案有关,但方式非常灵活。

您可以在此处获取代码:PPTopMostController

并使用最顶级的控制器

UIViewController *c = [UIViewController topMostController];

A
Ahmadreza

对于最新的 Swift 版本:
创建一个文件,将其命名为 UIWindowExtension.swift 并粘贴以下代码段:

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
}

在任何地方使用它:

if let topVC = getTopViewController() {

}

我不想过多地改变你的答案,但会提出一些建议。 1.增加对UISplitViewController的支持。 2. 使用 switch 而不是 if else。 3.不确定您是否也需要静态函数,我认为您可以在您声明的第一个实例级别 var 中轻松完成此操作。 4. 最好不要创建太多全局函数,但这是个人喜好问题。可以用一行代码实现全局函数的效果:UIApplication.sharedApplication().delegate?.window?.visibleViewController
J
JonasG

这是对 Eric 答案的改进:

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) 是一个辅助函数。

现在您需要做的就是调用 topMostController() 并返回最顶层的 UIViewController!


我会说从 1983 年开始。请记住,Objective-C 包含 C... 在 C 函数中包装 ObjC 代码是一种常见的做法,所以是的,这就是 Objective-C 代码。
@JonasG 嗨乔纳斯,在什么情况下你更喜欢在 C 中包装 ObjC 代码?因为,我有时看到这样的C函数并不能区分用法。在 C 中包装代码是否提供任何性能优势?
@OzBoz 在无法立即明确 self 应该属于哪个类的情况下。
C
Community

使用下面的扩展来获取当前可见的 UIViewController。适用于 Swift 4.0 及更高版本

Swift 4.0 及更高版本:

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

如何使用?

let objViewcontroller = UIApplication.topViewController()

UINavigationControllerUITabBarController 案例之前,不应该先对 presentedViewController 进行此测试吗?否则,如果视图控制器从 UINavigationControllerUITabBarController 模态呈现,它不会作为顶部视图控制器返回,即使它是可见的视图控制器。
F
FishStix
@implementation UIWindow (Extensions)

- (UIViewController*) topMostController
{
    UIViewController *topController = [self rootViewController];

    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }

    return topController;
}

@end

我认为您不满足原始帖子中所述的条件。
K
Kamran Khan

这是我对此的看法。感谢@Stakenborg 指出跳过将 UIAlertView 作为最顶层控制器的方法

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

您应该避免在 Objective-C 中将方法命名为 getSomething:。这具有特殊含义(更多:cocoadevcentral.com/articles/000082.php),您的代码中不满足这些要求。
B
Bartłomiej Semańczyk

Swift 中 UIApplication 的简单扩展:

笔记:

它关心 UITabBarController 内的 moreNavigationController

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

简单用法:

if let topViewController = UIApplication.topViewController() {
    //do sth with top view controller
}

是 是 是 - 网络上有很多解决方案可以找到 topMostViewController 但如果你的应用程序有带有更多选项卡的选项卡栏,你必须处理它有点不同。
l
lifuqing_ios
- (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;
    }
}

我使用了这个,但请注意,当存在多个视图控制器时它会中断
S
Saranjith

Swift 4.2 扩展

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

在任何地方使用它,例如,

 UIApplication.topViewController()?.present(yourController, animated: true, completion: nil)

或喜欢,

 UIApplication.topViewController()?
                    .navigationController?
                    .popToViewController(yourController,
                                         animated: true)

适合任何类,如 UINavigationController、UITabBarController

享受!


@BilalBakhrom 说“赞成。我认为你的答案是最好的。你不能直接调用 topViewController() 方法。UIApplication 类是单例的,使用一个名为“shared”的实例。”在 an edit 中,我已投票拒绝。如果这实际上是正确的,请click here to be taken to a menu where you can approve that edit.
'keyWindow' 在 iOS 13.0 中已弃用:不应用于支持多个场景的应用程序,因为它会在所有连接的场景中返回一个关键窗口
n
nalexn

Swift 4.2 中简洁而全面的解决方案,考虑了 UINavigationControllers、UITabBarControllers、呈现和子视图控制器:

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()
  }
}

用法:

let viewController = UIApplication.shared.topmostViewController()

M
Martin Algesten

另一个 Swift 解决方案

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)

}

T
Tom Andersen

这对我有用。

我发现有时控制器在键窗口上为零,因为 keyWindow 是一些操作系统的东西,比如警报等。

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

S
Stakenborg

扩展@Eric 的答案,您需要注意 keyWindow 实际上是您想要的窗口。例如,如果您在点击警报视图中的某些内容后尝试使用此方法,则 keyWindow 实际上将是警报的窗口,这无疑会给您带来问题。当我通过警报处理深层链接并导致 SIGABRTs 没有堆栈跟踪时,这发生在我身上。要调试的总婊子。

这是我现在使用的代码:

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

随意将其与从该问题的其他答案中检索您喜欢的顶视图控制器的任何风格混合。


您是否发现这是一个完整的解决方案?许多其他答案都非常复杂,试图解释如此多的边缘情况。我希望这是真的,它是如此简单和优雅。
我从来没有遇到过问题。如果您没有对导航堆栈做任何不寻常的事情,这应该可以工作,否则其他一些解决方案可以处理更复杂的情况。
E
Esqarrouth

替代 Swift 解决方案:

static func topMostController() -> UIViewController {
    var topController = UIApplication.sharedApplication().keyWindow?.rootViewController
    while (topController?.presentedViewController != nil) {
        topController = topController?.presentedViewController
    }

    return topController!
}

M
Marc Renaud

这个解决方案是最完整的。它考虑了: UINavigationController UIPageViewController UITabBarController 以及来自顶部视图控制器的最顶部呈现的视图控制器

该示例在 Swift 3 中。

有3个重载

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

E
Edward Novelo

Swift 中的绝佳解决方案,在 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
}

B
Ben Guild

很多这些答案是不完整的。尽管这是在 Objective-C 中,但这是我现在可以将它们组合在一起的最好的编译,作为一个非递归块:

链接到 Gist,以防它被修改:https://gist.github.com/benguild/0d149bb3caaabea2dac3d2dca58c0816

参考/比较代码:

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

A
Anil Arigela

我知道它很晚,可能是多余的。但以下是我想出的对我有用的片段:

    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
    }

B
Bobj-C

迅速:

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

用法:

 if let topController = window.visibleViewController() {
            println(topController)
        }

A
Aamir

我认为大多数答案都完全忽略了UINavigationViewController,因此我通过以下实现处理了这个用例。

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

R
Reporter

这非常适合从任何根视图控件中查找顶部 viewController 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]; 

T
Toland Hon

不确定这是否有助于您通过找到最顶层的视图控制器来完成您想要完成的工作,但我试图展示一个新的视图控制器,但如果我的根视图控制器已经有一个模式对话框,它将被阻止,所以我将使用以下代码循环到所有模态视图控制器的顶部:

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

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

T
Tapas Pal

你可以通过使用找到最顶层的视图控制器

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

除此之外,如果您实际阅读了这个问题,self 没有 navigationController 属性。