¿Llegar a UIViewController desde UIView?
¿ Existe una forma integrada de llegar de a UIView
a su UIViewController
? Sé que puedes acceder UIViewController
a su UIView
vía [self view]
, pero me preguntaba si hay una referencia inversa.
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 principal.
@interface UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController;
@end
@implementation UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController {
UIResponder *responder = [self nextResponder];
while (responder != nil) {
if ([responder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)responder;
}
responder = [responder nextResponder];
}
return nil;
}
@end
Para usar este código, agréguelo a un nuevo archivo de clase (yo llamé al mío "UIKitCategories") y elimine los datos de la clase... copie @interface en el encabezado y @implementation en el archivo .m. Luego, en su proyecto, #importe "UIKitCategories.h" y utilícelo dentro del código UIView:
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];
UIView
es una subclase 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 controlador de vista, lo devuelve -nextResponder
. Si no hay un controlador de vista, el método devolverá la supervisión.
Agregue esto a su proyecto y estará listo para comenzar.
@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.
Dado que esta ha sido la respuesta aceptada durante mucho tiempo, siento que necesito rectificarla con una respuesta mejor.
Algunos comentarios sobre la necesidad:
- Su vista no debería necesitar acceder directamente al controlador de vista.
- En cambio, la vista debería ser independiente del controlador de vista y poder funcionar en diferentes contextos.
- Si necesita que la vista interactúe de alguna manera con el controlador 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 UITableView
lo hace, por ejemplo) y no le importa si está implementada en el controlador de vista o en cualquier otra clase que termine usando.
Mi respuesta original es la siguiente: No recomiendo esto, ni el resto de respuestas donde se logra acceso directo al controlador de vista.
No existe una forma integrada de hacerlo. Si bien puede solucionarlo agregando un IBOutlet
y UIView
conectándolos en Interface Builder, no se recomienda. La vista no debe conocer el controlador de vista. En su lugar, debe hacer lo que sugiere @Phil M y crear un protocolo para usarlo como delegado.
Yo sugeriría un enfoque más ligero para recorrer 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