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

141

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

mahboudz
fuente
Creo que este hilo tiene las respuestas: ¿ Llegar a UIViewController desde UIView en iPhone?
Ushox
Básicamente, estoy tratando de llamar mi vista La vista del Controlador Aparecerá mientras mi vista se descarta. La vista está siendo descartada por la vista misma que está detectando un toque y llamando [self removeFromSuperview]; ViewController no está llamando a viewWillAppear / WillDisappear / DidAppear / DidDisappear.
mahboudz
Quise decir que estoy tratando de llamar a viewWillDisappear ya que mi vista se está descartando.
mahboudz
1
¿Posible duplicado de Get to UIViewController desde UIView?
Efren

Respuestas:

42

Sí, superviewes 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.

Dimitar Dimitrov
fuente
24
No estoy seguro de si rompería los principios de MVC. En cualquier punto, una vista solo tiene un controlador de vista. Poder acceder a él para devolverle un mensaje debe ser una función automática, no una en la que tenga que trabajar para lograrlo (agregando una propiedad para realizar un seguimiento). Se podría decir lo mismo sobre las opiniones: ¿por qué necesita saber quién es su hijo? O si hay otras vistas de hermanos. Sin embargo, hay formas de obtener esos objetos.
mahboudz
Tiene algo de razón acerca de la vista, ya que conoce su elemento primario, no es una decisión de diseño súper clara, pero ya está establecido para realizar algunas acciones, utilizando directamente una variable de miembro de la supervista (verificar el tipo principal, eliminar del elemento primario, etc.). Después de haber trabajado con PureMVC recientemente, me he vuelto un poco más quisquilloso con la abstracción del diseño :) Haría un paralelo entre las clases UIView y UIViewController de iPhone y las clases View y Mediator de PureMVC; la mayoría de las veces, la clase View no necesita Conozca su manejador / interfaz MVC (UIViewController / Mediator).
Dimitar Dimitrov
9
Palabra clave: "la mayoría".
Glenn Maynard
278

De la UIResponderdocumentació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 nextResponderhasta 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; \
})
mxcl
fuente
Solo una cosa: si le preocupa la contaminación por categorías, simplemente defínalo como una función estática en lugar de una macro. Además, el brutal tipo de letra es peligroso y, además, la macro puede no ser correcta, pero no estoy seguro.
mojuba
La macro funciona, solo uso la versión macro personalmente. Comienzo una gran cantidad de proyectos y tengo un encabezado que acabo de colocar en todas partes con un montón de estas funciones de macro utilidad. Me ahorra tiempo. Si no le gustan las macros, puede adaptarlas a una función, pero las funciones estáticas parecen tediosas, ya que debe colocar una en cada archivo que desee usar. ¿Parece que en su lugar desearía una función no estática declarada en un encabezado y definida en un .m en algún lugar?
mxcl
Es bueno evitar algunos delegados o notificaciones. ¡Gracias!
Ferran Maylinch 01 de
Debería convertirlo en una extensión de UIResponder;). Puesto muy edificante.
ScottyBlades
32

@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
Federico Zanetello
fuente
parentViewControllerno se puede definir publicsi la extensión está en el mismo archivo con el UIViewque puede configurarla fileprivate, se compila pero no funciona. 😐
Esta funcionando bien He usado esto para la acción del botón de retroceso dentro del archivo .xib de encabezado común.
McDonal_11
23

Solo para fines de depuración, puede llamar _viewDelegatea 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.
Leo Natan
fuente
Esto es exactamente por qué llegué a esta pregunta. Aparentemente, 'nextResponder' hace lo mismo, pero agradezco la información que proporciona esta respuesta. Entiendo y me gusta MVC, ¡pero la depuración es un animal diferente!
mbm29414
44
Parece que esto solo funciona en la vista principal del controlador de vista, no en ninguna subvista de la misma. _viewControllerForAncestorrecorrerá las supervistas hasta que encuentre la primera que pertenezca a un controlador de vista.
n00neimp0rtant
Gracias @ n00neimp0rtant! Estoy votando esta respuesta para que la gente vea tu comentario.
eyuelt
Actualiza la respuesta con más métodos.
Leo Natan
1
Esto es útil al depurar.
evanchin
10

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
Andrey
fuente
4

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)
}
iTSangar
fuente
2

Si no está familiarizado con el código y desea encontrar ViewController correspondiente a la vista dada, puede intentar:

  1. Ejecutar aplicación en depuración
  2. Navega a la pantalla
  3. Inspector de vista de inicio
  4. Tome la Vista que desea encontrar (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.

m4js7er
fuente
1

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

Nava Carmon
fuente
Si tiene un puñado de vistas, y una cierra todas las vistas, y necesita llamar a viewWillDisappear, ¿no sería más fácil para esa vista detectar el grifo que pasar el grifo al controlador de vista y hacer que el controlador de vista verifique con todas las vistas para ver cuál fue tocado?
mahboudz
0

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

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.

Morten Carlsen
fuente
0

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
  }                       
}
Hackbarth
fuente
-1

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

Scott McCoy
fuente
recursiveDescriptionsolo imprime la jerarquía de vistas , no los controladores de vista.
Alan Zeino
1
a partir de eso, puede identificar el viewcontroller @AlanZeino
Akshay