Добраться до UIViewController из UIView?

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

24 ответов


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

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

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

пример того, как это реализовать, следует:

@protocol MyViewDelegate < NSObject >

- (void)viewActionHappened;

@end

@interface MyView : UIView

@property (nonatomic, assign) MyViewDelegate delegate;

@end

@interface MyViewController < MyViewDelegate >

@end

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

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

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


используя пример, опубликованный 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];

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

добавьте это в ваш проект и вы готовы свернуть.

@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 есть метод работы для возврата контроллера вида.


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

@implementation MyUIViewSubclass

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

@end

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

@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

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

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


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


Я изменил 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)!
    }

Edit 2: - Swift Extention

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

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

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

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


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

//.h

@property (nonatomic, readonly) UIViewController * viewController;

//.м

- (UIViewController *)viewController
{
    for (UIResponder * nextResponder = self.nextResponder;
         nextResponder;
         nextResponder = nextResponder.nextResponder)
    {
        if ([nextResponder isKindOfClass:[UIViewController class]])
            return (UIViewController *)nextResponder;
    }

    // Not found
    NSLog(@"%@ doesn't seem to have a viewController". self);
    return nil;
}

самый простой цикл do while для поиска viewController.

-(UIViewController*)viewController
{
    UIResponder *nextResponder =  self;

    do
    {
        nextResponder = [nextResponder nextResponder];

        if ([nextResponder isKindOfClass:[UIViewController class]])
            return (UIViewController*)nextResponder;

    } while (nextResponder != nil);

    return nil;
}

я наткнулся на ситуацию, когда у меня есть небольшой компонент, который я хочу повторно использовать, и добавил некоторый код в самом многоразовом представлении(это действительно не намного больше, чем кнопка, которая открывает 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
    }
}

это не отвечает на вопрос напрямую, а делает предположение о намерениях вопроса.

если у вас есть представление, и в этом представлении вам нужно вызвать метод на другом объекте, например, контроллер представления, вы можете использовать NSNotificationCenter.

сначала создайте строку уведомления в заголовочном файле

#define SLCopyStringNotification @"ShaoloCopyStringNotification"

на ваш взгляд вызов postNotificationName:

- (IBAction) copyString:(id)sender
{
    [[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}

затем в контроллере вида вы добавляете наблюдатель. Я делаю это в viewDidLoad

- (void)viewDidLoad
{
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(copyString:)
                                                 name:SLCopyStringNotification
                                               object:nil];
}

теперь (также в том же контроллере представления) реализуйте свой метод copyString: как показано в @selector выше.

- (IBAction) copyString:(id)sender
{
    CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
    UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
    [gpBoard setString:result.stringResult];
}

Я не говорю, что это правильный способ сделать это, это просто кажется чище, чем запустить цепочку первого ответчика. Я использовал этот код для реализации UIMenuController в UITableView и передачи события обратно в UIViewController, чтобы я мог что-то сделать с данными.


это, безусловно, плохая идея и неправильный дизайн, но я уверен, что мы все можем наслаждаться быстрым решением лучшего ответа, предложенного @Phil_M:

static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
    func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
        if let nextResponder = responder.nextResponder() {
            if let nextResp = nextResponder as? UIViewController {
                return nextResp
            } else {
                return traverseResponderChainForUIViewController(nextResponder)
            }
        }
        return nil
    }

    return traverseResponderChainForUIViewController(responder)
}

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

if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}

все кредиты @Phil_M


ответ Фила:

в строку: id nextResponder = [self nextResponder]; Если self (UIView) не является подвидом ViewController, если вы знаете иерархию self(UIView), вы также можете использовать: id nextResponder = [[self superview] nextResponder];...


может, я опаздываю. Но в этой ситуации мне не нравится категория (загрязнения). Я люблю этот путь:

#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})

мое решение, вероятно, будет считаться фиктивным, но у меня была аналогичная ситуация, как mayoneez (я хотел переключить представления в ответ на жест в EAGLView), и я получил контроллер представления EAGL таким образом:

EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;

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

Я вижу аналогичную проблему, когда UIView в UIViewController реагирует на ситуацию, и ему нужно сначала сказать своему контроллеру родительского вида, чтобы скрыть кнопку "назад", а затем по завершении сообщить контроллеру родительского вида, что ему нужно выскочить из стека.

Я пробовал это с делегатами без успеха.

Я не понимаю, почему это должно быть плохая идея?


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


обновленная версия для swift 4: Спасибо за @Phil_M и @paul-slm

static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
    func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
        if let nextResponder = responder.next {
            if let nextResp = nextResponder as? UIViewController {
                return nextResp
            } else {
                return traverseResponderChainForUIViewController(responder: nextResponder)
            }
        }
        return nil
    }

    return traverseResponderChainForUIViewController(responder: responder)
}

Swift 4 версия

extension UIView {
var parentViewController: UIViewController? {
    var parentResponder: UIResponder? = self
    while parentResponder != nil {
        parentResponder = parentResponder!.next
        if let viewController = parentResponder as? UIViewController {
            return viewController
        }
    }
    return nil
}

}

пример использования

 if let parent = self.view.parentViewController{

    }

Swiftier решение

extension UIView {
    var parentViewController: UIViewController? {
        for responder in sequence(first: self, next: { .next }) {
            if let viewController = responder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

Если ваш rootViewController является UINavigationViewController, который был настроен в классе AppDelegate, то

    + (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];

for (UIViewController *v in arrVc)
{
    if ([v isKindOfClass:c])
    {
        return v;
    }
}

return nil;}

где c требуется класс контроллеров вида.

использование:

     RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];

нет никакой возможности.

Я передаю указатель UIViewController на UIView (или соответствующее наследование). Мне жаль, что я не могу помочь с подходом IB к проблеме, потому что я не верю в IB.

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