获取最高的UIViewController


191

如果UIViewController没有访问权限,我似乎无法获得最高排名UINavigationController。这是我到目前为止的内容:

UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(vc, animated: true, completion: nil)

但是,它似乎没有任何作用。在keyWindowrootViewController似乎是非零值太多,所以可选的链接不应该是一个问题。

注意: 做这样的事情是一个坏主意。它破坏了MVC模式。


这是一个可用的替代解决方案stackoverflow.com/a/39994115/1872233
iDevAmit

Answers:


283

presentViewController显示了一个视图控制器。它不返回视图控制器。如果您不使用UINavigationController,则可能正在寻找presentedViewController,您需要从根开始并遍历显示的视图。

if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

    // topController should now be your topmost view controller
}

对于Swift 3+:

if var topController = UIApplication.shared.keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

    // topController should now be your topmost view controller
}

对于iOS 13+

let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first

if var topController = keyWindow?.rootViewController {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }

// topController should now be your topmost view controller
}

1
有人可以解释while循环吗?对我来说,似乎没有什么可以循环的。我什至不知道为什么会编译。
汤姆教授(Tom)

15
@ProfessorTom只要topController.presentedViewController返回某些内容(即控制器具有显示的子控制器),循环就会继续。这是while let为了强制执行topController.presentedViewController必须返回某些内容的事实。如果返回nil(即,这是控制器没有显示的子代),则它将停止循环。在循环的主体中,它将子级重新分配为current topController,然后再次循环,沿视图控制器层次结构向下移动。它可以重新分配topController,因为它是一个var在外部if声明。
rickerbh

1
谢谢。我尚未在线找到任何示例while let。当然,if let可以找到很多例子。
汤姆教授

1
let x = somethingThatCouldBeNil语法是一个超级方便的技巧,以在任何地方使用,可以使用真值/状态。如果我们不在这里使用它,则必须显式分配一个值,然后进行测试以查看它是否确实存在。我认为这确实是简洁而富有表现力的。
rickerbh

1
我对这个技巧很熟悉,在while循环中推理要困难一些,为此我发现了很多例子,尤其是这个例子。
汤姆教授

270

有这个扩展名

斯威夫特2. *

extension UIApplication {
    class func topViewController(controller: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(selected)
            }
        }
        if let presented = controller?.presentedViewController {
            return topViewController(presented)
        }
        return controller
    }
}

迅捷3

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

您可以在控制器的任何地方使用它

if let topController = UIApplication.topViewController() {

}

1
感谢您的扩展技巧:)
Thein

4
我试图对该答案进行重要的编辑,但被拒绝了(我不知道为什么,给出的模板原因也没有道理):在递归使用nav.visibleViewController之前,请先检查其是否为nil很重要。调用(就像检查tab.selectedViewController的方式一样),因为否则,如果它为nil,则会陷入递归无限循环。
伊桑·G

@EthanG根据我的理解,如果nav.visibleViewController为nil,则该函数将返回nil(放到最后一个return)。它如何进入无限循环?
Desmond DAI'2

3
我认为这将是更符合逻辑的,使本作的UIViewController静电功能
莱谢克Zarna

1
如果您想在UITabBarControllers上捕获模态呈现的视图控制器,则应该首先呈现'presentedViewController'检查
。– Tokuriku

65

快速4/5 +获得最高的viewController

// MARK: UIApplication extensions

extension UIApplication {

    class func getTopViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return getTopViewController(base: nav.visibleViewController)

        } else if let tab = base as? UITabBarController, let selected = tab.selectedViewController {
            return getTopViewController(base: selected)

        } else if let presented = base?.presentedViewController {
            return getTopViewController(base: presented)
        }
        return base
    }
}

如何使用

if let topVC = UIApplication.getTopViewController() {
   topVC.view.addSubview(forgotPwdView)
}

2
出色的解决方案。谢谢!
安德烈·M

2
iOS 13.0中不推荐使用“ keyWindow”。
rs7

2
iOS 13.0中不推荐使用“ keyWindow” stackoverflow.com/a/57899013/4514671
Rebeloper

19
extension UIWindow {

    func visibleViewController() -> UIViewController? {
        if let rootViewController: UIViewController = self.rootViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
        }
        return nil
    }

    static func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {
        if let navigationController = vc as? UINavigationController,
            let visibleController = navigationController.visibleViewController  {
            return UIWindow.getVisibleViewControllerFrom( vc: visibleController )
        } else if let tabBarController = vc as? UITabBarController,
            let selectedTabController = tabBarController.selectedViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: selectedTabController )
        } else {
            if let presentedViewController = vc.presentedViewController {
                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)
            } else {
                return vc
            }
        }
    }
}

用法:

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

这个解决方案看起来确实很有希望,但是当我收到推送通知时,我尝试运行该解决方案以获取我正在使用的视图控制器,并且在return UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!)
Mike

@Mike,您只需要使用presentedViewController,而不是presentedViewController。presentedViewController
阿莱尔

@allaire如果您确实在模态视图控制器之上显示了模态视图控制器,那么您是否需要.presentedViewController.presentedViewController
Baran Emre

6

基于Dianz的答案,Objective-C版本

- (UIViewController *) topViewController {
   UIViewController *baseVC = UIApplication.sharedApplication.keyWindow.rootViewController;
   if ([baseVC isKindOfClass:[UINavigationController class]]) {
       return ((UINavigationController *)baseVC).visibleViewController;
   }

   if ([baseVC isKindOfClass:[UITabBarController class]]) {
       UIViewController *selectedTVC = ((UITabBarController*)baseVC).selectedViewController;
       if (selectedTVC) {
           return selectedTVC;
       }
   }

   if (baseVC.presentedViewController) {
       return baseVC.presentedViewController;
   }
   return baseVC;
}

对于UITabBarController中的UINavigationController不起作用。它将返回UINavigationController,应该在导航卡住的情况下返回topController。
Mike.R 16-10-23

Tnx Tnx Tnx Bro
reza_khalafi

6

我喜欢@dianz的答案,所以这里是它的迅捷3版本。基本上是同一回事,但是他缺少花括号,并且某些语法/变量/方法名称已更改。就是这里!

extension UIApplication {
    class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let nav = base as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }
        if let tab = base as? UITabBarController {
            if let selected = tab.selectedViewController {
                return topViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topViewController(base: presented)
        }
        return base
    }
}

用法仍然完全相同:

if let topController = UIApplication.topViewController() {
    print("The view controller you're looking at is: \(topController)")
}

6

https://gist.github.com/db0company/369bfa43cb84b145dfd8 我对该站点的答案和评论进行了一些测试。对我来说,以下作品

extension UIViewController {
    func topMostViewController() -> UIViewController {

        if let presented = self.presentedViewController {
            return presented.topMostViewController()
        }

        if let navigation = self as? UINavigationController {
            return navigation.visibleViewController?.topMostViewController() ?? navigation
        }

        if let tab = self as? UITabBarController {
            return tab.selectedViewController?.topMostViewController() ?? tab
    }

        return self
    }
}

extension UIApplication {
    func topMostViewController() -> UIViewController? {
        return self.keyWindow?.rootViewController?.topMostViewController()
    }
}

然后,通过以下方式获取顶部viewController:

UIApplication.shared.topMostViewController()

5

使用此代码查找最顶级的UIViewController

func getTopViewController() -> UIViewController? {
    var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
    while topController?.presentedViewController != nil {
        topController = topController?.presentedViewController
    }
    return topController
}

2
这与rickerbh的答案有何不同?
ElectroBuddha

5

@AlberZou使用计算变量而不是函数的轻微变化

extension UIViewController {
  var topMostViewController : UIViewController {

    if let presented = self.presentedViewController {
      return presented.topMostViewController
    }

    if let navigation = self as? UINavigationController {
      return navigation.visibleViewController?.topMostViewController ?? navigation
    }

    if let tab = self as? UITabBarController {
      return tab.selectedViewController?.topMostViewController ?? tab
    }

    return self
  }
}

extension UIApplication {
  var topMostViewController : UIViewController? {
    return self.keyWindow?.rootViewController?.topMostViewController
  }
}

然后说

if let topViewControler = UIApplication.shared.topMostViewController {
    ... do stuff
}

4

基于上面的Bob -c:

斯威夫特3.0

extension UIWindow {


    func visibleViewController() -> UIViewController? {
        if let rootViewController: UIViewController  = self.rootViewController {
            return UIWindow.getVisibleViewControllerFrom(vc: rootViewController)
        }
        return nil
    }

    class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController {

        if vc.isKind(of: UINavigationController.self) {

            let navigationController = vc as! UINavigationController
            return UIWindow.getVisibleViewControllerFrom( vc: navigationController.visibleViewController!)

        } else if vc.isKind(of: UITabBarController.self) {

            let tabBarController = vc as! UITabBarController
            return UIWindow.getVisibleViewControllerFrom(vc: tabBarController.selectedViewController!)

        } else {

            if let presentedViewController = vc.presentedViewController {

                return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController)

            } else {

                return vc;
            }
        }
    }
}

3

口味太多,但没有一个经过反复阐述。与以前的结合:

     func topMostController() -> UIViewController? {
        var from = UIApplication.shared.keyWindow?.rootViewController
        while (from != nil) {
            if let to = (from as? UITabBarController)?.selectedViewController {
                from = to
            } else if let to = (from as? UINavigationController)?.visibleViewController {
                from = to
            } else if let to = from?.presentedViewController {
                from = to
            } else {
                break
            }
        }
        return from
    }

2

您可以在AppDelegate中定义一个UIViewController变量,然后在每个viewWillAppear中将变量设置为self。(不过dianz的答案是最好的答案。)

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    let appDel = UIApplication.sharedApplication().delegate as! AppDelegate
    appDel.currentVC = self
}

1
非常感谢,它对我来说很好,作为另一个解决方案,当它尝试使NavigationControll返回nil时,我无法推送任何新的vc
Amr Angry

确保将currentVC定义为弱引用,否则将发生内存泄漏。
bubuxu

2

在Swift 3中找到可见的viewController

if let viewControllers = window?.rootViewController?.childViewControllers {

     let prefs = UserDefaults.standard

     if viewControllers[viewControllers.count - 1] is ABCController{
        print("[ABCController] is visible")

     }
}

此代码找到最后添加的或最后一个活动的控制器可见。

我已经在AppDelegate中使用它来查找活动视图控制器


2
import UIKit

extension UIApplication {

    // MARK: Choose keyWindow as per your choice
    var currentWindow: UIWindow? {
        connectedScenes
        .filter({$0.activationState == .foregroundActive})
        .map({$0 as? UIWindowScene})
        .compactMap({$0})
        .first?.windows
        .filter({$0.isKeyWindow}).first
    }

    // MARK: Choose keyWindow as per your choice
    var keyWindow: UIWindow? {
        UIApplication.shared.windows.first { $0.isKeyWindow }
    }

    class func topMostViewController(base: UIViewController? = UIApplication.shared.currentWindow?.rootViewController) -> UIViewController? {

        if let nav = base as? UINavigationController {
            return topMostViewController(base: nav.visibleViewController)
        }

        if let tab = base as? UITabBarController {
            let moreNavigationController = tab.moreNavigationController

            if let top = moreNavigationController.topViewController, top.view.window != nil {
                return topMostViewController(base: top)
            } else if let selected = tab.selectedViewController {
                return topMostViewController(base: selected)
            }
        }
        if let presented = base?.presentedViewController {
            return topMostViewController(base: presented)
        }
        return base
    }
}

“ visibleViewController”的模棱两可使用
Omar N Shamali

1

您将代码放在哪里?

我发现我在演示中尝试了您的代码,如果您将代码放入

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 

将失败,因为尚未设置密钥窗口。

但是我把你的代码放在了一些视图控制器中

override func viewDidLoad() {

它只是工作。


它不在didFinishLaunchingWithOptions。我只是出于各种调试目的而需要它。
Zoyt 2014年

1

在极少数情况下,使用自定义序列,最顶部的视图控制器不在导航堆栈或选项卡栏控制器中或未显示,而是将其视图插入到key windown子视图的顶部。

在这种情况下,有必要检查是否UIApplication.shared.keyWindow.subviews.last == self.view确定当前视图控制器是否是最顶级的。


1

对于任何寻求快速5 / iOS 13+解决方案的人(keywindow自iOS 13起已弃用)

extension UIApplication {

    class func getTopMostViewController() -> UIViewController? {
        let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
        if var topController = keyWindow?.rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            return topController
        } else {
            return nil
        }
    }
}

0
  var topViewController: UIViewController? {
        guard var topViewController = UIApplication.shared.keyWindow?.rootViewController else { return nil }
        while let presentedViewController = topViewController.presentedViewController {
            topViewController = presentedViewController
        }
        return topViewController
    }

0

对我来说最好的解决方案是使用功能扩展。使用此扩展名创建一个快速文件

首先是UIWindow扩展

public extension UIWindow {
    var visibleViewController: UIViewController? {
        return UIWindow.visibleVC(vc: self.rootViewController)
    }

    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

在该文件添加功能内

func visibleViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}

如果要使用它,则可以在任何地方调用它。 范例

  override func viewDidLoad() {
    super.viewDidLoad()
      if let topVC = visibleViewController() {
             //show some label or text field 
    }
}

文件代码是这样的

import UIKit

public extension UIWindow {
    var visibleViewController: UIViewController? {
        return UIWindow.visibleVC(vc: self.rootViewController)
    }

    static func visibleVC(vc: UIViewController?) -> UIViewController? {
        if let navigationViewController = vc as? UINavigationController {
            return UIWindow.visibleVC(vc: navigationViewController.visibleViewController)
        } else if let tabBarVC = vc as? UITabBarController {
            return UIWindow.visibleVC(vc: tabBarVC.selectedViewController)
        } else {
            if let presentedVC = vc?.presentedViewController {
                return UIWindow.visibleVC(vc: presentedVC)
            } else {
                return vc
            }
        }
    }
}

func visibleViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.