Dada una vista, ¿cómo obtengo su viewController?

Tengo un puntero a una UIView . ¿Cómo UIViewController su UIViewController ? [self superview] es otro UIView , pero no el UIViewController , ¿verdad?

Sí, la superview es la vista que contiene su vista. Su vista no debería saber cuál es exactamente su controlador de vista, porque eso rompería los principios de MVC.

El controlador, por otro lado, sabe de qué vista es responsable ( self.view = myView ) y, por lo general, esta vista delega los métodos / eventos para su manejo al controlador.

Normalmente, en lugar de un puntero a su vista, debe tener un puntero a su controlador, que a su vez puede ejecutar alguna lógica de control o pasar algo a su vista.

De la documentación de nextResponder para nextResponder :

La clase UIResponder no almacena ni configura el siguiente respondedor automáticamente, sino que devuelve nulo por defecto. Las subclases deben anular este método para configurar el siguiente respondedor. UIView implementa este método devolviendo el objeto UIViewController que lo administra (si tiene uno) o su supervista (si no lo hace) ; UIViewController implementa el método devolviendo la supervista de su vista; UIWindow devuelve el objeto de la aplicación y UIApplication devuelve nil.

Por lo tanto, si nextResponder el nextResponder una vista hasta que sea del tipo UIViewController , entonces tiene viewController padre de la vista.

Tenga en cuenta que aún puede no tener un controlador de vista principal. Pero solo si la vista no forma parte de la jerarquía de vistas de la vista de viewController.

Swift 3 y extensión de 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 extensión:

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

Categoría 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 

Esta macro evita la contaminación de categoría:

 #define UIViewParentController(__view) ({ \ UIResponder *__responder = __view; \ while ([__responder isKindOfClass:[UIView class]]) \ __responder = [__responder nextResponder]; \ (UIViewController *)__responder; \ }) 

Para fines de depuración únicamente, puede llamar a _viewDelegate en las vistas para obtener sus controladores de vista. Esta es una API privada, por lo que no es segura para App Store, pero para la depuración es útil.

Otros métodos útiles:

  • _viewControllerForAncestor : obtenga el primer controlador que administra una vista en la cadena de supervista. (gracias n00neimp0rtant)
  • _rootAncestorViewController : obtenga el controlador antecesor cuya jerarquía de vistas está configurada en la ventana actualmente.

Para obtener una referencia a UIViewController que tenga UIView, podría hacer la extensión de UIResponder (que es súper clase para UIView y UIViewController), lo que permite pasar por la cadena de respuesta y así llegar a UIViewController (de lo contrario, devolver cero).

 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 

@andrey responde en una línea (probado en Swift 4.1 ):

 extension UIResponder { var parentViewController: UIViewController? { return (self.next as? UIViewController) ?? self.next?.parentViewController } } 

uso:

  let vc: UIViewController? = view.getParentViewController 

La manera rápida y genérica en Swift 3:

 extension UIResponder { func parentController(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 no está familiarizado con el código y desea ver ViewController en correspondencia con la vista dada, puede intentar:

  1. Ejecutar aplicación en depuración
  2. Navega a la pantalla
  3. Comenzar inspector de vista
  4. Tome la vista que desea buscar (o una vista secundaria aún mejor)
  5. Desde el panel derecho, obtenga la dirección (por ejemplo, 0x7fe523bd3000)
  6. En la consola de depuración, comience a escribir comandos:
     po (UIView *) 0x7fe523bd3000
     po [(UIView *) 0x7fe523bd3000 nextResponder]
     po [[(UIView *) 0x7fe523bd3000 nextResponder] nextResponder]
     po [[[UIView *) 0x7fe523bd3000 nextResponder] nextResponder] nextResponder]
     ...

En la mayoría de los casos obtendrá UIView, pero de vez en cuando habrá una clase basada en UIViewController.

Creo que puedes propagar el tap al controlador de vista y dejar que lo maneje. Este es un enfoque más aceptable. En cuanto al acceso a un controlador de vista desde su vista, debe mantener una referencia a un controlador de vista, ya que no hay otra manera. Ver este hilo, podría ser útil: acceder al controlador de vista desde una vista

Más tipo de código seguro para 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 } } 

Por desgracia, esto es imposible a menos que subclases la vista y le proporciones una propiedad de instancia o similar que almacena la referencia del controlador de vista dentro una vez que la vista se agrega a la escena …

En la mayoría de los casos, es muy fácil evitar el problema original de esta publicación, ya que la mayoría de los controladores de vista son entidades bien conocidas para el progtwigdor que se encargó de agregar las subvistas a la Vista del ViewController 😉 Por eso supongo que Apple nunca me molesté en agregar esa propiedad.

Un poco tarde, pero aquí hay una extensión que te permite encontrar un respondedor de cualquier tipo, incluido 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 configura un punto de interrupción, puede pegar esto en el depurador para imprimir la jerarquía de vista:

 po [[UIWindow keyWindow] recursiveDescription] 

Deberías poder encontrar el padre de tu vista en algún lugar de ese lío 🙂