Étant donné une vue, comment obtenir son viewController?


141

J'ai un pointeur vers un fichier UIView. Comment y accéder UIViewController? [self superview]est un autre UIView, mais pas le UIViewController, non?



Fondamentalement, j'essaie d'appeler la vue de mon contrôleur viewWillAppear lorsque ma vue est rejetée. La vue est rejetée par la vue elle-même qui détecte un tap et appelle [self removeFromSuperview]; ViewController n'appelle pas viewWillAppear / WillDisappear / DidAppear / DidDisappear lui-même.
mahboudz

Je voulais dire que j'essaye d'appeler viewWillDisappear car ma vue est rejetée.
mahboudz le

Réponses:


42

Oui, superviewc'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.


24
Je ne sais pas si cela enfreindrait les principes MVC. À tout moment, une vue n'a qu'un seul contrôleur de vue. Pouvoir y accéder pour lui renvoyer un message devrait être une fonctionnalité automatique, et non une fonctionnalité pour laquelle vous devez travailler (en ajoutant une propriété pour garder une trace). On pourrait dire la même chose des vues: pourquoi avez-vous besoin de savoir qui vous êtes l'enfant? Ou s'il existe d'autres points de vue frères et sœurs. Pourtant, il existe des moyens d'obtenir ces objets.
mahboudz le

Vous avez un peu raison sur la vue, connaissant son parent, ce n'est pas une décision de conception très claire, mais il est déjà établi pour effectuer certaines actions, en utilisant directement une variable de membre superview (vérifier le type de parent, supprimer du parent, etc.). Ayant récemment travaillé avec PureMVC, je suis devenu un peu plus pointilleux sur l'abstraction du design :) Je ferais un parallèle entre les classes UIView et UIViewController de l'iPhone et les classes View et Mediator de PureMVC - la plupart du temps, la classe View n'a pas besoin de connaître son gestionnaire / interface MVC (UIViewController / Mediator).
Dimitar Dimitrov le

9
Mot clé: "le plus".
Glenn Maynard

278

De la UIResponderdocumentation 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 nextResponderjusqu'à 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; \
})

Juste une chose: si vous vous inquiétez de la pollution de catégorie, définissez-la simplement comme une fonction statique plutôt que comme une macro. De plus, le typage brutal est dangereux, et en plus de cela, la macro peut ne pas être correcte mais je ne suis pas sûr.
mojuba

La macro fonctionne, je n'utilise que la version macro personnellement. Je démarre beaucoup de projets et j'ai un en-tête que je viens de déposer partout avec un tas de ces fonctions utilitaires macro. Cela me fait gagner du temps. Si vous n'aimez pas les macros, vous pouvez les adapter en fonction, mais les fonctions statiques semblent fastidieuses, car vous devez ensuite en mettre une dans chaque fichier que vous souhaitez utiliser. On dirait que vous voudriez plutôt une fonction non statique déclarée dans un en-tête et définie dans un .m quelque part?
mxcl

Bien pour éviter certains délégués ou notifications. Merci!
Ferran Maylinch

Vous devriez en faire une extension de UIResponder;). Article très édifiant.
ScottyBlades

32

@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

parentViewControllerne peut pas être défini publicsi l'extension est dans le même fichier que UIViewvous pouvez la définir fileprivate, elle compile mais cela ne fonctionne pas! 😐

Cela fonctionne bien. Je l'ai utilisé pour l'action du bouton retour dans le fichier .xib d'en-tête commun.
McDonal_11

23

À 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.

C'est exactement pourquoi je suis venu à cette question. Apparemment, «nextResponder» fait la même chose, mais j'apprécie la perspicacité que cette réponse fournit. Je comprends et j'aime MVC, mais le débogage est un autre animal!
mbm29414

4
Il semble que cela ne fonctionne que sur la vue principale du contrôleur de vue, pas sur les sous-vues de celui-ci. _viewControllerForAncestorparcourra les super-vues jusqu'à ce qu'il trouve la première qui appartient à un contrôleur de vue.
n00neimp0rtant

Merci @ n00neimp0rtant! Je vote pour cette réponse pour que les gens voient votre commentaire.
eyuelt

Mettez à jour la réponse avec plus de méthodes.
Leo Natan

1
Ceci est utile lors du débogage.
evanchin le

10

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

4

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

2

Si vous n'êtes pas familier avec le code et que vous souhaitez trouver ViewController correspondant à une vue donnée, vous pouvez essayer:

  1. Exécuter l'application dans le débogage
  2. Accédez à l'écran
  3. Commencer l'inspecteur de vue
  4. Saisissez la vue que vous souhaitez rechercher (ou une vue enfant encore mieux)
  5. Dans le volet de droite, obtenez l'adresse (par exemple 0x7fe523bd3000)
  6. Dans la console de débogage, commencez à écrire des commandes:
    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.


1

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


Si vous avez une poignée de vues et que l'on ferme toutes les vues et que vous devez appeler viewWillDisappear, ne serait-il pas plus facile pour cette vue de détecter le robinet que de remettre le robinet au contrôleur de vue et de faire vérifier le contrôleur de vue avec toutes les vues pour voir laquelle a été tapée?
mahboudz le

0

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

0

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é.


0

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

-1

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 :)


recursiveDescriptionimprime uniquement la hiérarchie des vues , pas les contrôleurs de vue.
Alan Zeino

1
à partir de là, vous pouvez identifier le viewcontroller @AlanZeino
Akshay
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.