我遇到了一些情况,现在,它将是方便的,能够找到“最顶层”的视图控制器(一个负责当前视图),但还没有找到一种方法。
基本上,挑战是这样的:给定一个在一个类中执行,这个类不是一个视图控制器(或一个视图)[并且没有活动视图的地址],并且没有传递最顶层视图控制器的地址(或者,比如说,导航控制器的地址),是否有可能找到那个视图控制器?(如果是的话,是怎么做到的?)
或者,如果找不到,有没有可能找到最高处的风景?
我遇到了一些情况,现在,它将是方便的,能够找到“最顶层”的视图控制器(一个负责当前视图),但还没有找到一种方法。
基本上,挑战是这样的:给定一个在一个类中执行,这个类不是一个视图控制器(或一个视图)[并且没有活动视图的地址],并且没有传递最顶层视图控制器的地址(或者,比如说,导航控制器的地址),是否有可能找到那个视图控制器?(如果是的话,是怎么做到的?)
或者,如果找不到,有没有可能找到最高处的风景?
当前回答
使用下面的扩展抓取当前可见的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()
其他回答
很多答案都是不完整的。虽然这是在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;
};
下面两个函数可以帮助在视图控制器堆栈中找到topViewController。以后可能需要自定义,但是对于这段代码来说,理解topViewController或viewcontroller堆栈的概念非常棒。
- (UIViewController*)findTopViewController {
id topControler = [self topMostController];
UIViewController* topViewController;
if([topControler isKindOfClass:[UINavigationController class]]) {
topViewController = [[(UINavigationController*)topControler viewControllers] lastObject];
} else if ([topControler isKindOfClass:[UITabBarController class]]) {
//Here you can get reference of top viewcontroller from stack of viewcontrollers on UITabBarController
} else {
//topController is a preented viewController
topViewController = (UIViewController*)topControler;
}
//NSLog(@"Top ViewController is: %@",NSStringFromClass([topController class]));
return topViewController;
}
- (UIViewController*)topMostController
{
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
//NSLog(@"Top View is: %@",NSStringFromClass([topController class]));
return topController;
}
你可以使用[viewController Class]方法找出一个viewController的类的类型。
扩展@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;
}
你可以把这个和你喜欢的从这个问题的其他答案中检索顶视图控制器的方法混合在一起。
这是对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;
}
UIViewController *cont是一个辅助函数。
现在你所需要做的就是调用topMostController()和最顶端的UIViewController应该被返回!
为了避免很多复杂性,我通过在委托中创建一个viewController来跟踪当前的viewController,并在每个viewDidLoad方法中设置它为self,这样每当你加载一个新视图时,委托中持有的viewController将对应于该视图的viewController。这可能是丑陋的,但它工作得很好,没有必要有一个导航控制器或任何废话。