J'ai un pointeur sur une UIView
. Comment accéder à sa UIViewController
? [self superview]
est une autre UIView
, mais pas la UIViewController
, non?
Oui, la superview
est la vue qui contient votre vue. Votre vue ne doit pas savoir quel est exactement son contrôleur de vue, car cela enfreindrait les principes de MVC.
Le contrôleur, quant à lui, sait de quelle vue il est responsable (self.view = myView
) et généralement, cette vue délègue des méthodes/événements à gérer.
Généralement, au lieu d'un pointeur sur votre vue, vous devez avoir un pointeur sur votre contrôleur, qui peut à son tour exécuter une logique de contrôle ou transmettre quelque chose à sa vue.
Dans la documentation UIResponder
pour nextResponder
:
La classe UIResponder ne stocke pas ou ne définit pas le répondeur suivant automatiquement, mais renvoie par défaut nil. Les sous-classes doivent remplacer cette méthode pour définir le prochain répondeur. UIView implémente cette méthode en renvoyant l’objet UIViewController qui la gère (s’il en a un) ou son aperçu (si ce n’est pas le cas) ; UIViewController implémente la méthode en renvoyant l’aperçu de sa vue; UIWindow renvoie l'objet d'application et UIApplication renvoie nil.
Ainsi, si vous recréez la vue nextResponder
d’une vue jusqu’à ce qu'elle soit de type UIViewController
, vous avez le viewController parent d’une vue.
Notez qu'il peut toujours pas avoir un contrôleur de vue parent. Mais uniquement si la vue ne fait pas partie de la hiérarchie de vues d’un viewController.
Swift 3 et Swift 4.1 extension:
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:
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
}
}
Catégorie 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
Cette macro évite la pollution de catégorie:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
À des fins de débogage uniquement, vous pouvez appeler _viewDelegate
sur les vues pour obtenir leurs contrôleurs de vue. Ceci est une API privée, donc non sécurisée pour App Store, mais elle est utile pour le débogage.
Autres méthodes utiles:
_viewControllerForAncestor
- récupère le premier contrôleur qui gère une vue dans la chaîne superview. (merci n00neimp0rtant)_rootAncestorViewController
- récupère le contrôleur ancêtre dont la hiérarchie de vues est définie dans la fenêtre.@andrey répond en une ligne (testé dans Swift 4.1 ):
extension UIResponder {
public var parentViewController: UIViewController? {
return next as? UIViewController ?? next?.parentViewController
}
}
usage:
let vc: UIViewController = view.parentViewController
Pour obtenir une référence à UIViewController ayant UIView, vous pouvez faire une extension de UIResponder (qui est une super classe pour UIView et UIViewController), ce qui permet de remonter dans la chaîne de répondeurs et d'atteindre ainsi UIViewController (sinon, renvoie zéro).
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
La manière rapide et générique dans 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)
}
Si vous ne connaissez pas le code et que vous souhaitez trouver ViewController correspondant à la vue donnée, vous pouvez essayer:
po (UIView *) 0x7fe523bd3000 po [(UIView *) 0x7fe523bd3000 nextResponder] po [[((UIView *) 0x7fe523bd3000 nextResponder] nextResponder] po [[[((UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder] ...
Dans la plupart des cas, vous obtiendrez UIView, mais de temps en temps, il y aura une classe basée sur UIViewController.
Je pense que vous pouvez propager le tap sur le contrôleur de vue et le laisser le gérer. C'est une approche plus acceptable. En ce qui concerne l'accès à un contrôleur de vue à partir de sa vue, vous devez conserver une référence à un contrôleur de vue, car il n'y a pas d'autre moyen. Voir ce fil, cela pourrait aider: Accéder au contrôleur de vue à partir d’une vue
Un peu tard, mais voici une extension qui vous permet de trouver un répondeur de tout type, y compris 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
}
}
Hélas, cela est impossible à moins de sous-classer la vue et de lui fournir une propriété d'instance ou similaire qui stocke la référence du contrôleur de vue à l'intérieur une fois que la vue est ajoutée à la scène ...
Dans la plupart des cas - il est très facile de contourner le problème initial de ce message, car la plupart des contrôleurs de vue sont des entités bien connues du programmeur, qui était chargé d'ajouter des jamais pris la peine d'ajouter cette propriété.
Plus de code sécurisé de type pour 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
}
}