Llegar a UIViewController desde UIView?

¿Existe una forma incorporada de pasar de una UIView a su UIViewController ? Sé que puede obtener de UIViewController a su UIView través de [self view] pero me preguntaba si hay una referencia inversa.

Como esta ha sido la respuesta aceptada por mucho tiempo, siento que necesito rectificarla con una mejor respuesta.

Algunos comentarios sobre la necesidad:

  • Su vista no debería necesitar acceder directamente al controlador de vista.
  • La vista debería ser independiente del controlador de vista y poder trabajar en diferentes contextos.
  • Si necesita la vista para interactuar de una manera con el controlador de vista, la forma recomendada y lo que Apple hace en Cocoa es usar el patrón de delegado.

Un ejemplo de cómo implementarlo sigue:

 @protocol MyViewDelegate < NSObject > - (void)viewActionHappened; @end @interface MyView : UIView @property (nonatomic, assign) MyViewDelegate delegate; @end @interface MyViewController < MyViewDelegate > @end 

La vista interactúa con su delegado (como hace UITableView , por ejemplo) y no importa si se implementa en el controlador de vista o en cualquier otra clase que termine usando.

Mi respuesta original es la siguiente: no recomiendo esto, ni el rest de las respuestas donde se logra el acceso directo al controlador de vista

No hay una forma incorporada de hacerlo. Si bien puede sortearlo agregando un IBOutlet en UIView y conectándolo en Interface Builder, esto no es recomendable. La vista no debería saber sobre el controlador de vista. En su lugar, debe hacer lo que @Phil M sugiere y crear un protocolo para ser utilizado como delegado.

Usando el ejemplo publicado por Brock, lo modifiqué para que sea una categoría de UIView en lugar de UIViewController y lo hice recursivo para que cualquier subvista pueda (con suerte) encontrar el UIViewController padre.

 @interface UIView (FindUIViewController) - (UIViewController *) firstAvailableUIViewController; - (id) traverseResponderChainForUIViewController; @end @implementation UIView (FindUIViewController) - (UIViewController *) firstAvailableUIViewController { // convenience function for casting and to "mask" the recursive function return (UIViewController *)[self traverseResponderChainForUIViewController]; } - (id) traverseResponderChainForUIViewController { id nextResponder = [self nextResponder]; if ([nextResponder isKindOfClass:[UIViewController class]]) { return nextResponder; } else if ([nextResponder isKindOfClass:[UIView class]]) { return [nextResponder traverseResponderChainForUIViewController]; } else { return nil; } } @end 

Para usar este código, agréguelo a un nuevo archivo de clase (llamé al mío “UIKitCategories”) y elimine los datos de clase … copie la interfaz @ en el encabezado y la @implementación en el archivo .m. Luego, en su proyecto, importe “UIKitCategories.h” y use dentro del código de UIView:

 // from a UIView subclass... returns nil if UIViewController not available UIViewController * myController = [self firstAvailableUIViewController]; 

UIView es una subclase de UIResponder . UIResponder establece el método -nextResponder con una implementación que devuelve nil . UIView anula este método, como se documenta en UIResponder (por alguna razón en lugar de en UIView ) de la siguiente manera: si la vista tiene un controlador de vista, es devuelto por -nextResponder . Si no hay controlador de vista, el método devolverá la supervista.

Agregue esto a su proyecto y estará listo para rodar.

 @interface UIView (APIFix) - (UIViewController *)viewController; @end @implementation UIView (APIFix) - (UIViewController *)viewController { if ([self.nextResponder isKindOfClass:UIViewController.class]) return (UIViewController *)self.nextResponder; else return nil; } @end 

Ahora UIView tiene un método de trabajo para devolver el controlador de vista.

Sugeriría un enfoque más liviano para atravesar la cadena de respuesta completa sin tener que agregar una categoría en UIView:

 @implementation MyUIViewSubclass - (UIViewController *)viewController { UIResponder *responder = self; while (![responder isKindOfClass:[UIViewController class]]) { responder = [responder nextResponder]; if (nil == responder) { break; } } return (UIViewController *)responder; } @end 

Combinando varias respuestas ya dadas, la envío también con mi implementación:

 @implementation UIView (AppNameAdditions) - (UIViewController *)appName_viewController { /// Finds the view's view controller. // Take the view controller class object here and avoid sending the same message iteratively unnecessarily. Class vcc = [UIViewController class]; // Traverse responder chain. Return first found view controller, which will be the view's view controller. UIResponder *responder = self; while ((responder = [responder nextResponder])) if ([responder isKindOfClass: vcc]) return (UIViewController *)responder; // If the view controller isn't found, return nil. return nil; } @end 

La categoría es parte de mi biblioteca estática habilitada para ARC que envío en cada aplicación que creo. Ha sido probado varias veces y no encontré ningún problema o fuga.

PD: No necesita usar una categoría como yo si la vista en cuestión es una subclase suya. En este último caso, simplemente ponga el método en su subclase y estará listo para continuar.

Aunque técnicamente se puede resolver como recomienda pgb , en mi humilde opinión, este es un error de diseño. La vista no debería necesitar conocer el controlador.

UIViewController respuesta para poder pasar cualquier vista, botón, etiqueta, etc. para obtener su UIViewController padre. Aquí está mi código.

 +(UIViewController *)viewController:(id)view { UIResponder *responder = view; while (![responder isKindOfClass:[UIViewController class]]) { responder = [responder nextResponder]; if (nil == responder) { break; } } return (UIViewController *)responder; } 

Editar la versión Swift 3

 class func viewController(_ view: UIView) -> UIViewController { var responder: UIResponder? = view while !(responder is UIViewController) { responder = responder?.next if nil == responder { break } } return (responder as? UIViewController)! } 

Editar 2: – Swift Extention

 extension UIView { //Get Parent View Controller from any view func parentViewController() -> UIViewController { var responder: UIResponder? = self while !(responder is UIViewController) { responder = responder?.next if nil == responder { break } } return (responder as? UIViewController)! } } 

No olvide que puede obtener acceso al controlador de vista raíz para la ventana desde la cual la vista es una subvista. A partir de ahí, si está utilizando, por ejemplo, un controlador de vista de navegación y desea insertar una nueva vista en él:

  [[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES]; 

Sin embargo, primero tendrá que configurar la propiedad rootViewController de la ventana correctamente. Haga esto cuando crea el controlador, por ejemplo, en el delegado de su aplicación:

 -(void) applicationDidFinishLaunching:(UIApplication *)application { window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; RootViewController *controller = [[YourRootViewController] alloc] init]; [window setRootViewController: controller]; navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController]; [controller release]; [window addSubview:[[self navigationController] view]]; [window makeKeyAndVisible]; } 

Si bien estas respuestas son técnicamente correctas, incluido Ushox, creo que la forma aprobada es implementar un nuevo protocolo o reutilizar uno existente. Un protocolo aísla al observador de lo observado, como colocar una ranura de correo entre ellos. En efecto, eso es lo que hace Gabriel a través de la invocación del método pushViewController; la vista “sabe” que es un protocolo apropiado pedir cortésmente a su navigationController que presione una vista, ya que viewController se ajusta al protocolo navigationController. Si bien puedes crear tu propio protocolo, simplemente usar el ejemplo de Gabriel y volver a utilizar el protocolo UINavigationController está bien.

No creo que sea una “mala” idea descubrir quién es el controlador de visualización para algunos casos. Lo que podría ser una mala idea es guardar la referencia a este controlador, ya que podría cambiar al cambiar las supervistas. En mi caso, tengo un getter que atraviesa la cadena de respuesta.

//.h

 @property (nonatomic, readonly) UIViewController * viewController; 

//.metro

 - (UIViewController *)viewController { for (UIResponder * nextResponder = self.nextResponder; nextResponder; nextResponder = nextResponder.nextResponder) { if ([nextResponder isKindOfClass:[UIViewController class]]) return (UIViewController *)nextResponder; } // Not found NSLog(@"%@ doesn't seem to have a viewController". self); return nil; } 

El ciclo más simple de hacer while para encontrar viewController.

 -(UIViewController*)viewController { UIResponder *nextResponder = self; do { nextResponder = [nextResponder nextResponder]; if ([nextResponder isKindOfClass:[UIViewController class]]) return (UIViewController*)nextResponder; } while (nextResponder != nil); return nil; } 

Me encontré con una situación en la que tengo un pequeño componente que quiero reutilizar, y agregué un código en una vista reutilizable (en realidad no es mucho más que un botón que abre un PopoverController ).

Si bien esto funciona bien en el iPad (el UIPopoverController presenta a sí mismo, por lo tanto no necesita referencia de un UIViewController ), obtener el mismo código para trabajar significa de repente hacer referencia a su presente presentViewController desde su UIViewController . Un poco inconsistente ¿verdad?

Como se mencionó anteriormente, no es el mejor enfoque para tener lógica en su UIView. Pero se sentía realmente inútil para envolver las pocas líneas de código necesarias en un controlador por separado.

De cualquier manera, aquí hay una solución rápida, que agrega una nueva propiedad a cualquier UIView:

 extension UIView { var viewController: UIViewController? { var responder: UIResponder? = self while responder != nil { if let responder = responder as? UIViewController { return responder } responder = responder?.nextResponder() } return nil } } 

Esto no responde la pregunta directamente, sino que hace una suposición sobre el propósito de la pregunta.

Si tiene una vista y en esa vista necesita llamar a un método en otro objeto, como por ejemplo, el controlador de vista, puede usar NSNotificationCenter en su lugar.

Primero crea tu cadena de notificación en un archivo de cabecera

 #define SLCopyStringNotification @"ShaoloCopyStringNotification" 

En su vista, llame a postNotificationName:

 - (IBAction) copyString:(id)sender { [[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil]; } 

Luego, en su controlador de vista, agrega un observador. Hago esto en viewDidLoad

 - (void)viewDidLoad { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(copyString:) name:SLCopyStringNotification object:nil]; } 

Ahora (también en el mismo controlador de vista) implementa tu método copyString: como se muestra en el @selector anterior.

 - (IBAction) copyString:(id)sender { CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)]; UIPasteboard *gpBoard = [UIPasteboard generalPasteboard]; [gpBoard setString:result.stringResult]; } 

No digo que esta sea la manera correcta de hacerlo, simplemente parece más limpio que correr en la cadena de primeros respondedores. Usé este código para implementar un UIMenuController en una UITableView y pasar el evento de vuelta al UIViewController para que pueda hacer algo con los datos.

Sin duda, es una mala idea y un diseño incorrecto, pero estoy seguro de que todos podemos disfrutar de una solución Swift de la mejor respuesta propuesta por @Phil_M:

 static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? { func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? { if let nextResponder = responder.nextResponder() { if let nextResp = nextResponder as? UIViewController { return nextResp } else { return traverseResponderChainForUIViewController(nextResponder) } } return nil } return traverseResponderChainForUIViewController(responder) } 

Si su intención es hacer cosas simples, como mostrar un cuadro de diálogo modal o datos de seguimiento, eso no justifica el uso de un protocolo. Personalmente almaceno esta función en un objeto de utilidad, puede usarlo desde cualquier cosa que implemente el protocolo UIResponder como:

 if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {} 

Todo el crédito a @Phil_M

Para la respuesta de Phil:

En línea: id nextResponder = [self nextResponder]; si self (UIView) no es una subvista de la vista de ViewController, si conoce la jerarquía de self (UIView) puede usar también: id nextResponder = [[self superview] nextResponder];

Quizás estoy tarde aquí. Pero en esta situación no me gusta la categoría (contaminación). Me encanta de esta manera:

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

Mi solución probablemente sería considerada como algo falso, pero tuve una situación similar a la de mayoneez (quería cambiar las vistas en respuesta a un gesto en un EAGLView), y obtuve el controlador de vista de EAGL de esta manera:

 EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController; 

Creo que hay un caso en que las necesidades observadas informan al observador.

Veo un problema similar cuando la UIView en un UIViewController está respondiendo a una situación y primero necesita decirle a su controlador de vista padre que oculte el botón Atrás y luego, al finalizar, le dice al controlador de vista padre que necesita salir de la stack.

He estado intentando esto con los delegates sin éxito.

No entiendo por qué esto debería ser una mala idea.

Otra manera fácil es tener su propia clase de vista y agregar una propiedad del controlador de vista en la clase de vista. Por lo general, el controlador de vista crea la vista y es allí donde el controlador puede establecerse en la propiedad. Básicamente es en lugar de buscar (con un poco de piratería) para el controlador, tener el controlador para establecerse a la vista – esto es simple, pero tiene sentido porque es el controlador que “controla” la vista.

Versión actualizada para swift 4: Gracias por @Phil_M y @ paul-slm

 static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? { func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? { if let nextResponder = responder.next { if let nextResp = nextResponder as? UIViewController { return nextResp } else { return traverseResponderChainForUIViewController(responder: nextResponder) } } return nil } return traverseResponderChainForUIViewController(responder: responder) } 

Swift 4 versión

 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 } 

}

Ejemplo de uso

  if let parent = self.view.parentViewController{ } 

Solución Swiftier

 extension UIView { var parentViewController: UIViewController? { for responder in sequence(first: self, next: { $0.next }) { if let viewController = responder as? UIViewController { return viewController } } return nil } } 

Si su rootViewController es UINavigationViewController, que se configuró en la clase AppDelegate, entonces

  + (UIViewController *) getNearestViewController:(Class) c { NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers]; for (UIViewController *v in arrVc) { if ([v isKindOfClass:c]) { return v; } } return nil;} 

Donde c requirió ver la clase de controladores.

USO:

  RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]]; 

No hay forma.

Lo que hago es pasar el puntero UIViewController a UIView (o una herencia apropiada). Lamento no poder ayudar con el enfoque de IB al problema porque no creo en IB.

Para responder al primer comentario: a veces es necesario saber quién lo llamó porque determina lo que puede hacer. Por ejemplo, con una base de datos puede que solo tenga acceso de lectura o lectura / escritura …