web-dev-qa-db-fra.com

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

J'ai un pointeur sur une UIView. Comment accéder à sa UIViewController? [self superview] est une autre UIView, mais pas la UIViewController, non?

115
mahboudz

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.

38
Dimitar Dimitrov

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; \
})
238
mxcl

À 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.
19
Leo Natan

@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
16

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
9
Andrey

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

Si vous ne connaissez pas le code et que vous souhaitez trouver ViewController correspondant à la vue donnée, vous pouvez essayer:

  1. Exécuter l'application en débogage
  2. Naviguer vers l'écran
  3. Lancer l'inspecteur de vue
  4. Saisissez la vue que vous voulez trouver (ou une vue enfant encore mieux)
  5. Dans le volet de droite, récupérez 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
m4js7er

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

0
Nava Carmon

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

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

0
Morten Carlsen

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
    }
}
0
Denis Krivitski