Как найти самый верхний контроллер вида на iOS - PullRequest
236 голосов
/ 26 мая 2011

Я столкнулся с парой случаев, когда было бы удобно найти "самый верхний" контроллер представления (тот, который отвечает за текущее представление), но не нашел способа сделать это.

В основном проблема заключается в следующем: учитывая, что один выполняется в классе, который не является контроллером представления (или представление) [и не имеет адреса активного вида] и не был передан адрес самого верхнего контроллера вида (или, скажем, адрес контроллера навигации), возможно ли найти этот контроллер вида?(И если да, то как?)

Или, если это не удастся, можно ли найти самый верхний вид?

Ответы [ 40 ]

0 голосов
/ 28 января 2019

Моя проблема была немного другой, я использовал SWRevealViewController в моем приложении. Я использовал ответ Юхена Чжуна , но он всегда возвращает topViewController как SWRevealViewController. Для тех, кто использует SWRevealViewController или какой-либо другой модуль для разработки sideMenu. Вот мое продолжение ответа Ючен Чжун:

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
}
}
0 голосов
/ 19 декабря 2018

Краткое, но всеобъемлющее решение в 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()
0 голосов
/ 21 октября 2018

Я думаю, что, возможно, здесь упускают одну вещь.Возможно, лучше передать родительский viewController в функцию, которая использует viewController.Если вы просматриваете иерархию представлений, чтобы найти контроллер верхнего вида, это, вероятно, нарушает разделение слоя модели и слоя пользовательского интерфейса и является запахом кода.Просто отметив это, я сделал то же самое, а затем понял, что гораздо проще просто передать его в функцию, вернув операцию модели на уровень пользовательского интерфейса, где у меня есть ссылка на контроллер представления.

0 голосов
/ 04 февраля 2014

Если корневой контроллер является контроллером навигации, правильный способ найти наиболее видимый контроллер:

UIViewController *rootVC = [[UIApplication sharedApplication] keyWindow].rootViewController;
if ([rootVC respondsToSelector:@selector(visibleViewController)])
{
    UIViewController *topVC = [(UINavigationController *)rootVC visibleViewController];
    // do your thing with topVC
}

Вот выдержка из UINavigationController.h:

@property(nonatomic,readonly,retain) UIViewController *topViewController; // The top view controller on the stack.
@property(nonatomic,readonly,retain) UIViewController *visibleViewController; // Return modal view controller if it exists. Otherwise the top view controller.
0 голосов
/ 07 декабря 2014

Другое решение опирается на цепочку респондента, которая может работать или не работать в зависимости от того, кто является первым респондентом:

  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 голосов
/ 29 октября 2013

Это прекрасно подходит для поиска контроллера верхнего вида 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 голосов
/ 31 марта 2015

Чтобы избежать большой сложности, я отслеживаю текущий viewController, создавая viewController в делегате и установив его в себя внутри каждого метода viewDidLoad, таким образом, каждый раз, когда вы загружаете новый вид, ViewController, содержащийся в делегате, будет соответствовать это представление viewController. Это может быть некрасиво, но прекрасно работает, и нет необходимости иметь навигационный контроллер или что-то в этом духе.

0 голосов
/ 30 мая 2016

Вот реализация Swift приложения с UINavigationController в качестве рута.

  if let nav = UIApplication.sharedApplication().keyWindow?.rootViewController as? UINavigationController{
        //get the current's navigation view controller
        var vc = nav.topViewController
        while vc?.presentedViewController != nil {
            vc = vc?.presentedViewController
        }
        return vc
    }
0 голосов
/ 23 апреля 2015

Предыдущий ответ, по-видимому, не обрабатывает случаи, когда 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 голосов
/ 17 мая 2015

Я думаю, что решение Раджеша почти идеальное, но я думаю, что лучше обходить подпредставления сверху вниз, я изменил на следующее:

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