Tengo un puntero a a UIView
. ¿Cómo accedo a su UIViewController
? [self superview]
es otro UIView
, pero no el UIViewController
, ¿verdad?
Tengo un puntero a a UIView
. ¿Cómo accedo a su UIViewController
? [self superview]
es otro UIView
, pero no el UIViewController
, ¿verdad?
Respuestas:
Sí, 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 generalmente, esta vista delega métodos / eventos para su manejo al controlador.
Por lo general, 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 UIResponder
documentación para nextResponder
:
La clase UIResponder no almacena ni establece el siguiente respondedor automáticamente, sino que devuelve nil por defecto. Las subclases deben anular este método para establecer el próximo respondedor. UIView implementa este método devolviendo el objeto UIViewController que lo administra (si tiene uno) o su supervista (si no lo tiene) ; UIViewController implementa el método devolviendo la vista general de su vista; UIWindow devuelve el objeto de la aplicación y UIApplication devuelve nil.
Por lo tanto, si repite una vista nextResponder
hasta que sea de tipo UIViewController
, entonces tiene el control de vista principal de cualquier 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 una vista del Controlador.
Extensión Swift 3 y 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
}
}
Extensión 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
}
}
Categoría objetivo-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 categoría de contaminación:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})
UIResponder
;). Puesto muy edificante.
@andrey responde en una línea (probado en Swift 4.1 ):
extension UIResponder {
public var parentViewController: UIViewController? {
return next as? UIViewController ?? next?.parentViewController
}
}
uso:
let vc: UIViewController = view.parentViewController
parentViewController
no se puede definir public
si la extensión está en el mismo archivo con el UIView
que puede configurarla fileprivate
, se compila pero no funciona. 😐
Solo para fines de depuración, puede llamar _viewDelegate
a las vistas para obtener sus controladores de vista. Esta es una API privada, por lo que no es segura para App Store, pero es útil para la depuración.
Otros métodos útiles:
_viewControllerForAncestor
- Obtenga el primer controlador que gestiona una vista en la cadena de supervista. (gracias n00neimp0rtant)_rootAncestorViewController
- Obtener el controlador ancestro cuya jerarquía de vista está configurada en la ventana actualmente._viewControllerForAncestor
recorrerá las supervistas hasta que encuentre la primera que pertenezca a un controlador de vista.
Para obtener referencia a que UIViewController tiene UIView, puede hacer una extensión de UIResponder (que es una superclase para UIView y UIViewController), que permite subir a través de la cadena de respuesta y así llegar a UIViewController (de lo contrario, devuelve nulo).
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 forma rápida y genérica en 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 no está familiarizado con el código y desea encontrar ViewController correspondiente a la vista dada, puede intentar:
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 puede propagar el grifo al controlador de vista y dejar que lo maneje. Este es un enfoque más aceptable. En cuanto a acceder a un controlador de vista desde su vista, debe mantener una referencia a un controlador de vista, ya que no hay otra manera. Vea este hilo, podría ayudar: Acceder al controlador de vista desde una vista
Más código de tipo 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 subclasifique la vista y le proporcione una propiedad de instancia o similar que almacene la referencia del controlador de vista dentro de ella una vez que la vista se agregue a la escena ...
En la mayoría de los casos, es muy fácil solucionar el problema original de esta publicación, ya que la mayoría de los controladores de vista son entidades conocidas para el programador que fue responsable de agregar subvistas a la Vista de ViewController ;-) Por eso supongo que Apple nunca se molestó en agregar esa propiedad.
Un poco tarde, pero aquí hay una extensión que le 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 establece un punto de interrupción, puede pegarlo en el depurador para imprimir la jerarquía de vistas:
po [[UIWindow keyWindow] recursiveDescription]
Debería poder encontrar el padre de su vista en algún lugar de ese desastre :)
recursiveDescription
solo imprime la jerarquía de vistas , no los controladores de vista.