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?


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

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.


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

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

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.

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

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

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.


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


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

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.


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

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


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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.