¿Llegas a UIViewController desde UIView?

¿Hay una forma integrada de pasar de una UIView a su UIViewController ? Sé que puedes get de UIViewController a su UIView través de [self view] pero me preguntaba si hay una reference inversa.

Como esta ha sido la respuesta aceptada por mucho time, creo que necesito rectificarlo con una mejor respuesta.

Algunos comentarios sobre la necesidad:

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

A continuación se muestra un ejemplo de cómo implementarlo:

 @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 le importa si está implementado en el controller de vista o en cualquier otra class que termine de usar.

Mi respuesta original sigue: No recomiendo esto, ni el rest de las respuestas donde se logra el acceso directo al controller de vista

No hay una forma incorporada para hacerlo. Si bien puedes IBOutlet agregando un IBOutlet en UIView y conectándolos en Interface Builder, esto no es recomendable. La vista no debe saber sobre el controller de vista. En cambio, debe hacer lo que sugiere @Phil M y crear un protocolo que se utilizará como delegado.

Usando el ejemplo publicado por Brock, lo modifiqué para que sea una categoría de UIView en lugar de UIViewController y lo hizo 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 file de class (nombre mío "UIKitCategories") y elimine los datos de class … copie @interface en el encabezado y @implementation en el file .m. Luego, en su proyecto, #importe "UIKitCategories.h" y use dentro del código UIView:

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

UIView es una subclass de UIResponder . UIResponder presenta 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 controller de vista, se devuelve mediante -nextResponder . Si no hay un controller de vista, el método devolverá la supervisión.

Agregue esto a su proyecto y ya está 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 controller de vista.

Sugiero 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, también lo estoy enviando 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 todas las aplicaciones que creo. Ha sido probado varias veces y no encontré problemas o filtraciones.

PS: No necesitas usar una categoría como la que hice si la vista en cuestión es una subclass tuya. En este último caso, simplemente coloque el método en su subclass y estará listo para hacerlo.

Aunque esto técnicamente se puede resolver como lo recomienda pgb , en mi humilde opinión, este es un defecto de layout. La vista no debería ser consciente del controller.

No olvide que puede get acceso al controller de vista raíz para la window en la que la vista es una subvista. A partir de ahí, si está utilizando, por ejemplo, un controller de vista de navigation y desea insert una nueva vista en él:

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

Sin embargo, primero deberá configurar correctamente la propiedad rootViewController de la window. Haga esto cuando cree por primera vez el controller, por ejemplo, en su delegado de la 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, incluida Ushox, creo que la manera aprobada es implementar un nuevo protocolo o reutilizar uno existente. Un protocolo aísla al observador de lo observado, algo así como poner una ranura de correo entre ellos. En efecto, eso es lo que Gabriel hace a través de la invocación del método pushViewController; la vista "sabe" que es un protocolo adecuado para pedir educadamente a su control de navigation que empuje una vista, ya que viewController se ajusta al protocolo navigationController. Si bien puede crear su propio protocolo, simplemente usando el ejemplo de Gabriel y reutilizando el protocolo UINavigationController está bien.

UIViewController respuesta para que pueda pasar cualquier vista, button, label, etc. para get 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; } 

Edición de 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)! } 

No creo que sea una idea "mala" averiguar quién es el controller de vista para algunos casos. Lo que podría ser una mala idea es save la reference a este controller, ya que podría cambiar al igual que las superventas cambian. En mi caso tengo un getter que atraviesa la cadena de respuesta.

//.marido

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

Lo más simple hacer while loop para encontrar el viewController.

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

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

Si bien esto funciona bien en el iPad (el UIPopoverController presenta por sí mismo, por lo tanto, no necesita reference alguna a un UIViewController ), get el mismo código para que funcione significa hacer reference de repente a your presentViewController desde su UIViewController . Un poco inconsistente ¿cierto?

Como se mencionó anteriormente, no es el mejor enfoque para tener lógica en tu UIView. Pero se sentía realmente inútil envolver las pocas líneas de código necesarias en un controller 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 directamente a la pregunta, sino que hace una suposition acerca de la intención de la pregunta.

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

Primero crea tu cadena de notificación en un file de encabezado

 #define SLCopyStringNotification @"ShaoloCopyStringNotification" 

En su vista, llame a postNotificationName:

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

Luego, en su controller de vista, agregue un observador. Lo hago en viewDidLoad

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

Ahora (también en el mismo controller de vista) implementa su método copyString: como se muestra en el @selector de arriba.

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

No estoy diciendo que esta sea la forma correcta de hacerlo, simplemente parece más limpio que ejecutar la primera cadena de respuesta. Utilicé este código para implementar un UIMenuController en un UITableView y pasar el evento de nuevo a UIViewController para que pueda hacer algo con los datos.

A la respuesta de Phil:

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

Seguramente es una mala idea y un layout equivocado, 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 dialog modal o datos de rastreo, eso no justifica el uso de un protocolo. Personalmente, guardo esta function en un object de utilidad, puedes usarla desde cualquier cosa que implemente el protocolo UESubunturador como:

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

Todo el crédito a @Phil_M

Probablemente, mi solución se consideraría tipo de falso, pero tuve una situación similar a mayoneez (quería cambiar las vistas en respuesta a un gesto en una EAGLView), y obtuve el controller de vista de EAGL de esta manera:

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

Creo que hay un caso cuando el observador necesita informar al observador.

Veo un problema similar en el que UIView en un UIViewController está respondiendo a una situación y primero debe decirle a su controller primario de vista que oculte el button de retroceso y luego, una vez completado, dígale al controller primario de vista que necesita salir de la stack.

He intentado esto con los delegates sin éxito.

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

Otra forma fácil es tener su propia class de vista y agregar una propiedad del controller de vista en la class de vista. Por lo general, el controller de vista crea la vista y ahí es donde el controller puede configurarse a sí mismo en la propiedad. Básicamente es en lugar de search (con un poco de piratería) para el controller, tener el controller para configurarse en la vista, esto es simple pero tiene sentido porque es el controller el que "controla" la vista.

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

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

Donde c requiere la class de controlleres de vista.

USO:

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

No hay forma

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

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