Получить UIViewController из UIView?

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

Ответ 1

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

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

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

Пример реализации:

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

Ответ 2

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

Ответ 3

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

Ответ 4

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

@implementation MyUIViewSubclass

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

@end

Ответ 5

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

@implementation UIView (AppNameAdditions)

- (UIViewController *)appName_viewController {
    /// Finds the view 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 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, которую я отправляю на каждое созданное мной приложение. Он был протестирован несколько раз, и я не обнаружил никаких проблем или утечек.

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

Ответ 6

Даже если это технически можно решить, как рекомендует pgb, IMHO, это недостаток дизайна. В представлении не должно быть необходимости знать контроллер.

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

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

Ответ 11

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

//. Ч

@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;
}

Ответ 12

Простейший цикл while для нахождения viewController.

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

    do
    {
        nextResponder = [nextResponder nextResponder];

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

    } while (nextResponder != nil);

    return nil;
}

Ответ 13

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

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

Ответ 14

Это, безусловно, плохая идея и неправильный дизайн, но я уверен, что мы все можем наслаждаться быстрым решением лучшего ответа, предложенного @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

Ответ 15

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

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

Ответ 16

Быстрое решение

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

Ответ 17

Swift 4

(более краткими, чем другие ответы)

fileprivate extension UIView {

  var firstViewController: UIViewController? {
    let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
    return firstViewController as? UIViewController
  }

}

Мой прецедент, для которого мне нужно получить доступ к представлению сначала UIViewController: у меня есть объект, который обертывает AVPlayer/AVPlayerViewController и я хочу предоставить простой метод show(in view: UIView) который будет внедрять AVPlayerViewController в view. Для этого мне нужно открыть view UIViewController.

Ответ 18

Версия 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{

 }

Ответ 19

Ответ Фила:

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

Ответ 20

Обновленная версия для быстрого 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)
}

Ответ 21

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

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

Ответ 22

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

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

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

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

Ответ 23

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

Ответ 24

Если вы не собираетесь загружать это в App Store, вы также можете использовать закрытый метод UIView.

@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end

// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];

Ответ 25

Если ваш 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]];

Ответ 26

Нет способа.

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

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