У меня есть указатель на UIView
. Как мне получить к нему доступ UIViewController
? [self superview]
это другое UIView
, но не то UIViewController
, правда ?
У меня есть указатель на UIView
. Как мне получить к нему доступ UIViewController
? [self superview]
это другое UIView
, но не то UIViewController
, правда ?
Ответы:
Да, superview
это представление, которое содержит ваше представление. Ваше представление не должно знать, какой именно контроллер представления является его контроллером представления, потому что это нарушит принципы MVC.
С другой стороны, контроллер знает, за какое представление он отвечает ( self.view = myView
), и обычно это представление делегирует методы / события для обработки контроллеру.
Как правило, вместо указателя на ваше представление у вас должен быть указатель на ваш контроллер, который, в свою очередь, может либо выполнять некоторую управляющую логику, либо передавать что-то своему представлению.
Из 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; \
})
UIResponder
;). Очень поучительный пост.
@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
, оно компилируется, но не работает! 😐
Только для целей отладки вы можете вызывать _viewDelegate
представления, чтобы получить их контроллеры представлений. Это частный API, поэтому он небезопасен для App Store, но полезен для отладки.
Другие полезные методы:
_viewControllerForAncestor
- получить первый контроллер, который управляет представлением в цепочке супервизора. (спасибо n00neimp0rtant)_rootAncestorViewController
- получить родительский контроллер, иерархия представлений которого установлена в текущем окне._viewControllerForAncestor
будет проходить по супервизорам, пока не найдет первый, принадлежащий контроллеру представления.
Чтобы получить ссылку на 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
Быстрый и универсальный способ в 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)
}
Если вы не знакомы с кодом и хотите найти ViewController, соответствующий данному представлению, вы можете попробовать:
po (UIView *) 0x7fe523bd3000 po [(UIView *) 0x7fe523bd3000 nextResponder] po [[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] po [[[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder] ...
В большинстве случаев вы получите UIView, но время от времени будет класс на основе UIViewController.
Я думаю, вы можете передать касание контроллеру представления и позволить ему обрабатывать его. Это более приемлемый подход. Что касается доступа к контроллеру представления из его представления, вы должны поддерживать ссылку на контроллер представления, поскольку другого пути нет. См. Эту ветку, это может помочь: Доступ к контроллеру представления из представления
В UIView есть переменная под названием window?
Итак, это вернет контроллер представления
вы можете получить к нему доступ, как это self.window? .rootViewController
Больше безопасного кода для 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
}
}
Увы, это невозможно, если вы не подклассифицируете представление и не предоставите ему свойство экземпляра или что-то подобное, которое хранит ссылку на контроллер представления внутри него, когда представление добавлено в сцену ...
В большинстве случаев - очень легко обойти исходную проблему этого поста, поскольку большинство контроллеров представлений являются хорошо известными сущностями программисту, который отвечал за добавление любых подпредставлений в представление ViewController ;-) Вот почему я предполагаю, что Apple никогда не удосужился добавить это свойство.
Немного поздно, но вот расширение, которое позволит вам найти ответчика любого типа, включая 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
}
}
Если вы установите точку останова, вы можете вставить ее в отладчик, чтобы распечатать иерархию представления:
po [[UIWindow keyWindow] recursiveDescription]
Вы должны найти своего родителя где-нибудь в этом беспорядке :)
recursiveDescription
печатает только вид иерархии, а не смотреть контроллеров.