Добраться до UIViewController из UIView? - PullRequest
177 голосов
/ 27 августа 2009

Есть ли встроенный способ получить от UIView до UIViewController? Я знаю, что вы можете перейти от UIViewController к UIView через [self view], но мне было интересно, есть ли обратная ссылка?

Ответы [ 25 ]

199 голосов
/ 17 сентября 2010

Используя пример, опубликованный Brock, я изменил его так, чтобы это была категория UIView вместо UIViewController, и сделал его рекурсивным, чтобы любое подпредставление могло (надеюсь) найти родительский UIViewController.

@interface UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController;
- (id) traverseResponderChainForUIViewController;
@end

@implementation UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController {
    // convenience function for casting and to "mask" the recursive function
    return (UIViewController *)[self traverseResponderChainForUIViewController];
}

- (id) traverseResponderChainForUIViewController {
    id nextResponder = [self nextResponder];
    if ([nextResponder isKindOfClass:[UIViewController class]]) {
        return nextResponder;
    } else if ([nextResponder isKindOfClass:[UIView class]]) {
        return [nextResponder traverseResponderChainForUIViewController];
    } else {
        return nil;
    }
}
@end

Чтобы использовать этот код, добавьте его в новый файл класса (я назвал мой "UIKitCategories") и удалите данные класса ... скопируйте @interface в заголовок, а @implementation в .m файл. Затем в вашем проекте #import "UIKitCategories.h" и используйте его в коде UIView:

// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
112 голосов
/ 08 апреля 2010

UIView является подклассом UIResponder. UIResponder излагает метод -nextResponder с реализацией, которая возвращает nil. UIView переопределяет этот метод, как описано в UIResponder (по некоторым причинам, а не в UIView) следующим образом: если представление имеет контроллер представления, оно возвращается -nextResponder. Если нет контроллера представления, метод возвратит суперпредставление.

Добавьте это к своему проекту, и вы готовы к работе.

@interface UIView (APIFix)
- (UIViewController *)viewController;
@end

@implementation UIView (APIFix)

- (UIViewController *)viewController {
    if ([self.nextResponder isKindOfClass:UIViewController.class])
        return (UIViewController *)self.nextResponder;
    else
        return nil;
}
@end

Теперь UIView имеет рабочий метод для возврата контроллера представления.

45 голосов
/ 27 августа 2009

Поскольку это был принятый ответ в течение долгого времени, я чувствую, что мне нужно исправить его с лучшим ответом.

Некоторые комментарии по необходимости:

  • Вашему представлению не требуется прямой доступ к контроллеру представления.
  • Вместо этого представление должно быть независимым от контроллера представления и иметь возможность работать в разных контекстах.
  • Если вам необходимо, чтобы представление взаимодействовало с контроллером представления, рекомендуемый способ и то, что Apple делает через Какао, это использование шаблона делегата.

Ниже приведен пример того, как его реализовать:

@protocol MyViewDelegate < NSObject >

- (void)viewActionHappened;

@end

@interface MyView : UIView

@property (nonatomic, assign) MyViewDelegate delegate;

@end

@interface MyViewController < MyViewDelegate >

@end

Представление взаимодействует с его делегатом (как, например, UITableView), и ему все равно, реализовано ли оно в контроллере представления или в каком-либо другом классе, который вы в конечном итоге используете.

Мой оригинальный ответ следующий: Я не рекомендую этого, так же как и остальные ответы, где прямой доступ к контроллеру представления достигнут

Нет встроенного способа сделать это. Хотя вы можете обойти это, добавив IBOutlet на UIView и подключив их в Интерфейсном Разработчике, это не рекомендуется. Представление не должно знать о контроллере представления. Вместо этого вам следует поступить так, как предлагает @Phil M, и создать протокол для использования в качестве делегата.

32 голосов
/ 27 января 2012

Я бы предложил более легкий подход для обхода всей цепочки респондентов без добавления категории в UIView:

@implementation MyUIViewSubclass

- (UIViewController *)viewController {
    UIResponder *responder = self;
    while (![responder isKindOfClass:[UIViewController class]]) {
        responder = [responder nextResponder];
        if (nil == responder) {
            break;
        }
    }
    return (UIViewController *)responder;
}

@end
22 голосов
/ 10 июня 2012

Объединяя несколько уже предоставленных ответов, я добавляю и мою реализацию:

@implementation UIView (AppNameAdditions)

- (UIViewController *)appName_viewController {
    /// Finds the view's view controller.

    // Take the view controller class object here and avoid sending the same message iteratively unnecessarily.
    Class vcc = [UIViewController class];

    // Traverse responder chain. Return first found view controller, which will be the view's view controller.
    UIResponder *responder = self;
    while ((responder = [responder nextResponder]))
        if ([responder isKindOfClass: vcc])
            return (UIViewController *)responder;

    // If the view controller isn't found, return nil.
    return nil;
}

@end

Категория является частью моей статической библиотеки с поддержкой ARC, которую я поставляю в каждом создаваемом приложении. Он был проверен несколько раз, и я не нашел никаких проблем или утечек.

P.S .: Вам не нужно использовать категорию, как я, если рассматриваемый вид является вашим подклассом. В последнем случае просто поместите метод в свой подкласс, и все готово.

12 голосов
/ 27 августа 2009

Даже если технически это можно решить, как рекомендует pgb , ИМХО, это недостаток дизайна. Представление не должно знать о контроллере.

10 голосов
/ 05 января 2016

Я изменил de ответ, чтобы я мог передать любое представление, кнопку, метку и т. Д., Чтобы получить его родитель UIViewController Вот мой код

+(UIViewController *)viewController:(id)view {
    UIResponder *responder = view;
    while (![responder isKindOfClass:[UIViewController class]]) {
        responder = [responder nextResponder];
        if (nil == responder) {
            break;
        }
    }
    return (UIViewController *)responder;
}

Редактировать версию Swift 3

class func viewController(_ view: UIView) -> UIViewController {
        var responder: UIResponder? = view
        while !(responder is UIViewController) {
            responder = responder?.next
            if nil == responder {
                break
            }
        }
        return (responder as? UIViewController)!
    }

Редактировать 2: - Быстрое расширение

extension UIView
{
    //Get Parent View Controller from any view
    func parentViewController() -> UIViewController {
        var responder: UIResponder? = self
        while !(responder is UIViewController) {
            responder = responder?.next
            if nil == responder {
                break
            }
        }
        return (responder as? UIViewController)!
    }
}
7 голосов
/ 01 апреля 2011

Не забывайте, что вы можете получить доступ к корневому контроллеру представления для окна, представлением которого является подпредставление. Оттуда, если вы, например, используя контроллер вида навигации и хотите добавить на него новый вид:

    [[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];

Однако сначала вам нужно будет правильно настроить свойство rootViewController окна. Делайте это при первом создании контроллера, например в вашем приложении делегат:

-(void) applicationDidFinishLaunching:(UIApplication *)application {
    window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    RootViewController *controller = [[YourRootViewController] alloc] init];
    [window setRootViewController: controller];
    navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
    [controller release];
    [window addSubview:[[self navigationController] view]];
    [window makeKeyAndVisible];
}
6 голосов
/ 06 апреля 2011

Хотя эти ответы являются технически правильными, включая Ushox, я думаю, что утвержденный способ заключается в реализации нового протокола или повторном использовании существующего. Протокол изолирует наблюдателя от наблюдаемого, что-то вроде помещения между ними почтового слота. По сути, это то, что делает Габриэль через вызов метода pushViewController; представление «знает», что это правильный протокол, чтобы вежливо попросить ваш navigationController выдвинуть представление, поскольку viewController соответствует протоколу navigationController. Хотя вы можете создать свой собственный протокол, просто используйте пример Габриэля и повторно используйте протокол UINavigationController.

6 голосов
/ 05 августа 2016

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

Хотя в iPad это работает нормально (UIPopoverController представляет себя, для этого не требуется ссылка на UIViewController), получить тот же код для работы означает внезапную ссылку на ваш presentViewController из вашего UIViewController. Как-то не так, верно?

Как уже упоминалось ранее, логика в UIView не самый лучший. Но было действительно бесполезно заключать несколько строк кода, необходимых в отдельный контроллер.

В любом случае, вот быстрое решение, которое добавляет новое свойство к любому UIView:

extension UIView {

    var viewController: UIViewController? {

        var responder: UIResponder? = self

        while responder != nil {

            if let responder = responder as? UIViewController {
                return responder
            }
            responder = responder?.nextResponder()
        }
        return nil
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...