J'ai un pointeur vers un fichier UIView
. Comment y accéder UIViewController
? [self superview]
est un autre UIView
, mais pas le UIViewController
, non?
J'ai un pointeur vers un fichier UIView
. Comment y accéder UIViewController
? [self superview]
est un autre UIView
, mais pas le UIViewController
, non?
Réponses:
Oui, superview
c'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 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 au contrôleur.
En règle générale, au lieu d'un pointeur vers votre vue, vous devriez avoir un pointeur vers votre contrôleur, qui à son tour peut soit exécuter une logique de contrôle, soit transmettre quelque chose à sa vue.
De la UIResponder
documentation pour nextResponder
:
La classe UIResponder ne stocke pas ou ne définit pas automatiquement le répondeur suivant, mais renvoie nil par défaut. Les sous-classes doivent remplacer cette méthode pour définir le prochain répondeur.UIView implémente cette méthode en retournant l'objet UIViewController qui le gère (s'il en a un) ou son superview (si ce n'est pas le cas) ; UIViewController implémente la méthode en renvoyant la supervision de sa vue; UIWindow renvoie l'objet application et UIApplication renvoie nil.
Ainsi, si vous recurse une vue nextResponder
jusqu'à ce qu'elle soit de type UIViewController
, alors vous avez le viewController parent de n'importe quelle vue.
Notez qu'il peut toujours pas avoir de contrôleur de vue parent. Mais seulement si la vue ne fait pas partie de la hiérarchie de vues d'un viewController.
Swift 3 et Extension 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
}
}
Extension 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
}
}
Catégorie Objectif-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; \
})
UIResponder
;). Article très édifiant.
@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
parentViewController
ne peut pas être défini public
si l'extension est dans le même fichier que UIView
vous pouvez la définir fileprivate
, elle compile mais cela ne fonctionne pas! 😐
À des fins de débogage uniquement, vous pouvez appeler _viewDelegate
vues pour obtenir leurs contrôleurs de vue. C'est une API privée, donc pas sûre pour l'App Store, mais pour le débogage, c'est utile.
Autres méthodes utiles:
_viewControllerForAncestor
- obtenir 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 vue est actuellement définie dans la fenêtre._viewControllerForAncestor
parcourra les super-vues jusqu'à ce qu'il trouve la première qui appartient à un contrôleur de vue.
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 en retournant nil).
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 n'êtes pas familier avec le code et que vous souhaitez trouver ViewController correspondant à une 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 robinet au 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 depuis 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 peut aider: Accéder au contrôleur de vue à partir d'une vue
Plus de code de sécurité 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
}
}
Hélas, cela est impossible à moins que vous ne sous-classiez la vue et ne lui fournissiez 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 d'origine de ce message car la plupart des contrôleurs de vue sont des entités bien connues du programmeur qui était responsable de l'ajout de toutes les sous-vues à la vue de ViewController ;-) C'est pourquoi je suppose qu'Apple jamais pris la peine d'ajouter cette propriété.
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
}
}
Si vous définissez un point d'arrêt, vous pouvez le coller dans le débogueur pour imprimer la hiérarchie des vues:
po [[UIWindow keyWindow] recursiveDescription]
Vous devriez pouvoir trouver le parent de votre vue quelque part dans ce désordre :)
recursiveDescription
imprime uniquement la hiérarchie des vues , pas les contrôleurs de vue.