Учитывая представление, как мне получить его viewController?

144

У меня есть указатель на UIView. Как мне получить к нему доступ UIViewController? [self superview]это другое UIView, но не то UIViewController, правда ?

Махбудз
источник
Я думаю, что в этой ветке есть ответы: добраться до UIViewController из UIView на iPhone?
Ushox 03
По сути, я пытаюсь вызвать viewWillAppear моего viewController, поскольку мое представление отклоняется. Представление закрывается самим представлением, которое обнаруживает касание и вызывает [self removeFromSuperview]; ViewController сам не вызывает viewWillAppear / WillDisappear / DidAppear / DidDisappear.
mahboudz 03
Я имел в виду, что пытаюсь вызвать viewWillDisappear, поскольку мое представление отклоняется.
mahboudz
1
Возможный дубликат Get to UIViewController из UIView?
Эфрен

Ответы:

43

Да, superviewэто представление, которое содержит ваше представление. Ваше представление не должно знать, какой именно контроллер представления является его контроллером представления, потому что это нарушит принципы MVC.

С другой стороны, контроллер знает, за какое представление он отвечает ( self.view = myView), и обычно это представление делегирует методы / события для обработки контроллеру.

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

Димитар Димитров
источник
25
Я не уверен, что это нарушит принципы MVC. В любой момент у представления есть только один контроллер представления. Возможность добраться до него, чтобы передать ему сообщение, должна быть автоматической функцией, а не той, для достижения которой вам нужно работать (добавляя свойство для отслеживания). То же самое можно сказать и о взглядах: зачем вам знать, кто вы такой? Или есть ли другие взгляды братьев и сестер. Но есть способы получить эти предметы.
mahboudz
Вы в некоторой степени правы насчет представления, зная о его родительском элементе, это не очень четкое дизайнерское решение, но оно уже установлено для выполнения некоторых действий, напрямую используя переменную-член суперпредставления (проверить родительский тип, удалить из родительского и т. Д.). Недавно поработав с PureMVC, я стал немного более придирчив к абстракции дизайна :) Я бы провел параллель между классами iPhone UIView и UIViewController и классами PureMVC View и Mediator - в большинстве случаев класс View не нуждается в знать о его обработчике / интерфейсе MVC (UIViewController / Mediator).
Димитар Димитров
9
Ключевое слово: «самый».
Гленн Мейнард
284

Из UIResponderдокументации для nextResponder:

Класс UIResponder не сохраняет и не устанавливает следующего ответчика автоматически, вместо этого по умолчанию возвращает nil. Подклассы должны переопределить этот метод, чтобы установить следующего респондента. UIView реализует этот метод, возвращая объект UIViewController, который им управляет (если он есть) или его супервизор (если нет) ; UIViewController реализует метод, возвращая супервизор своего представления; UIWindow возвращает объект приложения, а UIApplication возвращает ноль.

Итак, если вы рекурсивно повторяете представление nextResponderдо тех пор , пока оно не станет типом UIViewController, тогда у вас будет родительский viewController любого представления.

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

Расширение Swift 3 и Swift 4.1 :

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

Расширение Swift 2:

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

Категория Objective-C:

@interface UIView (mxcl)
- (UIViewController *)parentViewController;
@end

@implementation UIView (mxcl)
- (UIViewController *)parentViewController {
    UIResponder *responder = self;
    while ([responder isKindOfClass:[UIView class]])
        responder = [responder nextResponder];
    return (UIViewController *)responder;
}
@end

Этот макрос позволяет избежать загрязнения категорий:

#define UIViewParentController(__view) ({ \
    UIResponder *__responder = __view; \
    while ([__responder isKindOfClass:[UIView class]]) \
        __responder = [__responder nextResponder]; \
    (UIViewController *)__responder; \
})
mxcl
источник
Только одно: если вы беспокоитесь о загрязнении категорий, просто определите его как статическую функцию, а не как макрос. Также опасно грубое приведение типов, и, кроме того, макрос может быть некорректным, но я не уверен.
Mojuba 05
Макрос работает, лично использую только макроверсию. Я начинаю много проектов и получаю заголовок, который я просто добавляю повсюду с кучей этих вспомогательных макросов. Экономит время. Если вам не нравятся макросы, вы можете адаптировать их к функциям, но статические функции кажутся утомительными, поскольку вы должны затем помещать их в каждый файл, который хотите использовать. Похоже, вместо этого вы хотите, чтобы нестатическая функция была объявлена ​​в заголовке и определена где-нибудь в .m?
mxcl 06
Приятно избегать некоторых делегатов или уведомлений. Благодарность!
Ферран Мэйлинч 01
Вы должны сделать его продолжением UIResponder;). Очень поучительный пост.
ScottyBlades
35

@andrey ответ в одну строку (проверено в Swift 4.1 ):

extension UIResponder {
    public var parentViewController: UIViewController? {
        return next as? UIViewController ?? next?.parentViewController
    }
}

Применение:

 let vc: UIViewController = view.parentViewController
Федерико Занетелло
источник
parentViewControllerне может быть определено, publicесли расширение находится в одном файле с вашим, UIViewвы можете установить его fileprivate, оно компилируется, но не работает! 😐
Работает нормально. Я использовал это для действия кнопки «Назад» в общем файле заголовка .xib.
McDonal_11
В Xamarin: public static class Extensions {public static UIViewController ParentViewController (это представление UIResponder) => (view.NextResponder as UIViewController) ?? view.NextResponder? .ParentViewController (); }
Softlion
23

Только для целей отладки вы можете вызывать _viewDelegateпредставления, чтобы получить их контроллеры представлений. Это частный API, поэтому он небезопасен для App Store, но полезен для отладки.

Другие полезные методы:

  • _viewControllerForAncestor- получить первый контроллер, который управляет представлением в цепочке супервизора. (спасибо n00neimp0rtant)
  • _rootAncestorViewController - получить родительский контроллер, иерархия представлений которого установлена ​​в текущем окне.
Лео Натан
источник
Именно поэтому я пришел к этому вопросу. По-видимому, nextResponder делает то же самое, но я ценю понимание, которое дает этот ответ. Я понимаю и люблю MVC, но отладка - другое дело!
mbm29414 07
4
Похоже, это работает только в основном представлении контроллера представления, а не на каких-либо его подчиненных представлениях. _viewControllerForAncestorбудет проходить по супервизорам, пока не найдет первый, принадлежащий контроллеру представления.
n00neimp0rtant
Спасибо @ n00neimp0rtant! Я поддерживаю этот ответ, чтобы люди видели ваш комментарий.
eyuelt
Обновите ответ с помощью дополнительных методов.
Лео Натан,
1
Это полезно при отладке.
evanchin
10

Чтобы получить ссылку на UIViewController, имеющий UIView, вы можете сделать расширение UIResponder (который является суперклассом для UIView и UIViewController), который позволяет подниматься по цепочке респондентов и, таким образом, достигать UIViewController (в противном случае возвращается ноль).

extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.nextResponder() is UIViewController {
            return self.nextResponder() as? UIViewController
        } else {
            if self.nextResponder() != nil {
                return (self.nextResponder()!).getParentViewController()
            }
            else {return nil}
        }
    }
}

//Swift 3
extension UIResponder {
    func getParentViewController() -> UIViewController? {
        if self.next is UIViewController {
            return self.next as? UIViewController
        } else {
            if self.next != nil {
                return (self.next!).getParentViewController()
            }
            else {return nil}
        }
    }
}

let vc = UIViewController()
let view = UIView()
vc.view.addSubview(view)
view.getParentViewController() //provide reference to vc
Андрей
источник
4

Быстрый и универсальный способ в Swift 3:

extension UIResponder {
    func parentController<T: UIViewController>(of type: T.Type) -> T? {
        guard let next = self.next else {
            return nil
        }
        return (next as? T) ?? next.parentController(of: T.self)
    }
}

//Use:
class MyView: UIView {
    ...
    let parentController = self.parentController(of: MyViewController.self)
}
iTSangar
источник
2

Если вы не знакомы с кодом и хотите найти ViewController, соответствующий данному представлению, вы можете попробовать:

  1. Запустить приложение в отладке
  2. Перейти на экран
  3. Запустить инспектор просмотра
  4. Возьмите вид, который хотите найти (или еще лучше детский вид)
  5. На правой панели получите адрес (например, 0x7fe523bd3000)
  6. В консоли отладки начните писать команды:
    po (UIView *) 0x7fe523bd3000
    po [(UIView *) 0x7fe523bd3000 nextResponder]
    po [[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder]
    po [[[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder]
    ...

В большинстве случаев вы получите UIView, но время от времени будет класс на основе UIViewController.

m4js7er
источник
1

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

Нава Кармон
источник
Если у вас есть несколько представлений, и одно закрывает все представления, и вам нужно вызвать viewWillDisappear, не было бы проще для этого представления обнаружить касание, чем передать касание контроллеру представления и проверить контроллер представления со всеми видами, чтобы увидеть, какой из них был выбран?
mahboudz
1

В UIView есть переменная под названием window?

Итак, это вернет контроллер представления

вы можете получить к нему доступ, как это self.window? .rootViewController

Кишор Кумар
источник
0

Больше безопасного кода для Swift 3.0

extension UIResponder {
    func owningViewController() -> UIViewController? {
        var nextResponser = self
        while let next = nextResponser.next {
            nextResponser = next
            if let vc = nextResponser as? UIViewController {
                return vc
            }
        }
        return nil
    }
}
Денис Кривицкий
источник
0

Увы, это невозможно, если вы не подклассифицируете представление и не предоставите ему свойство экземпляра или что-то подобное, которое хранит ссылку на контроллер представления внутри него, когда представление добавлено в сцену ...

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

Мортен Карлсен
источник
0

Немного поздно, но вот расширение, которое позволит вам найти ответчика любого типа, включая ViewController.

extension NSObject{
func findNext(type: AnyClass) -> Any{
    var resp = self as! UIResponder

    while !resp.isKind(of: type.self) && resp.next != nil
    {
        resp = resp.next!
    }

    return resp
  }                       
}
Хакбарт
источник
-1

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

po [[UIWindow keyWindow] recursiveDescription]

Вы должны найти своего родителя где-нибудь в этом беспорядке :)

Скотт Маккой
источник
recursiveDescriptionпечатает только вид иерархии, а не смотреть контроллеров.
Алан Зейно,
1
из этого вы можете идентифицировать viewcontroller @AlanZeino
Акшай Паканати