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


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

24 169

24 ответа:

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

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

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

пример его реализации следующий:

@protocol MyViewDelegate < NSObject >

- (void)viewActionHappened;

@end

@interface MyView : UIView

@property (nonatomic, assign) MyViewDelegate delegate;

@end

@interface MyViewController < MyViewDelegate >

@end

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

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

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

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

Я бы предложил более легкий подход для обхода всей цепочки ответчиков без необходимости добавлять категорию В 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

категория является частью моего 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;
}

Edit Swift 3 Version

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];
-(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 version

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{

    }

более быстрое решение

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.

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