¿Hay una forma integrada de llegar de a UIView
a su UIViewController
? Sé que puede llegar UIViewController
a su UIView
vía, [self view]
pero me preguntaba si hay una referencia inversa.
¿Hay una forma integrada de llegar de a UIView
a su UIViewController
? Sé que puede llegar UIViewController
a su UIView
vía, [self view]
pero me preguntaba si hay una referencia inversa.
Respuestas:
Como esta ha sido la respuesta aceptada durante mucho tiempo, siento que necesito rectificarla con una respuesta mejor.
Algunos comentarios sobre la necesidad:
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 lo UITableView
hace, por ejemplo) y no le importa si se implementa en el controlador de vista o en cualquier otra clase que termine usando.
Mi respuesta original sigue: no recomiendo esto, ni el resto de las respuestas donde se logra el acceso directo al controlador de vista
No hay una forma integrada de hacerlo. Mientras que usted puede conseguir alrededor de él mediante la adición de una IBOutlet
sobre la UIView
y la conexión de estos en Interface Builder, esto no es recomendable. La vista no debe saber sobre el controlador de vista. En su lugar, debe hacer lo que @Phil M sugiere y crear un protocolo para usar 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 la clase ... copie la @interface en el encabezado y la @implementation en el archivo .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 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, es devuelta por -nextResponder
. Si no hay un controlador de vista, el método devolverá la supervista.
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.
UIView
y el UIViewController
. La respuesta de Phil M con recursión es el camino a seguir.
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, también lo envío 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 lo hice si la vista en cuestión es una subclase suya. En el último caso, simplemente coloque el método en su subclase y estará listo.
Aunque técnicamente esto se puede resolver como lo recomienda pgb , en mi humilde opinión, este es un defecto de diseño. La vista no debería necesitar ser consciente del controlador.
He modificado de respuesta, así que puede pasar cualquier punto de vista, botón, etiqueta, etc. conseguirlo de los padres UIViewController
. 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 versión de 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)!
}
Edición 2: - Extensión rápida
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 de la que 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:
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
Sin embargo, primero deberá configurar correctamente la propiedad rootViewController de la ventana. Haga esto cuando cree el controlador por primera vez, 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];
}
[[self navigationController] view]
es la vista (principal) "principal" de la ventana, la rootViewController
propiedad de la ventana debe configurarse para navigationController
que controle la vista "principal" de inmediato.
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, 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 apropiado pedir educadamente a su navigationController 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.
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 (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
presente en sí mismo, por lo tanto, no necesita hacer referencia a a UIViewController
), hacer que el mismo código funcione significa que de repente haga referencia presentViewController
a su UIViewController
. Un poco inconsistente ¿verdad?
Como se mencionó anteriormente, no es el mejor enfoque tener lógica en su UIView. Pero se sintió realmente inútil envolver las pocas líneas de código necesarias en un controlador 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
}
}
No creo que sea "mala" idea averiguar quién es el controlador de la vista en algunos casos. Lo que podría ser una mala idea es guardar la referencia a este controlador, ya que podría cambiar al igual que cambian las supervistas. En mi caso, tengo un captador 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 bucle while más simple 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;
}
(más conciso que las otras respuestas)
fileprivate extension UIView {
var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}
}
Mi caso de uso para el que tenga que acceder a la vista primero UIViewController
: Tengo un objeto que se envuelve alrededor AVPlayer
/ AVPlayerViewController
y quiero dar un simple show(in view: UIView)
método que va a incrustar AVPlayerViewController
en view
. Para eso, necesito acceder a view
's UIViewController
.
Esto no responde la pregunta directamente, sino que supone una suposición sobre la intención 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 el NSNotificationCenter en su lugar.
Primero cree su cadena de notificación en un archivo de encabezado
#define SLCopyStringNotification @"ShaoloCopyStringNotification"
En su opinión, 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) implemente su 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 forma correcta de hacer esto, 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 nuevamente al UIViewController para poder hacer algo con los datos.
Seguramente 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 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 usarla desde cualquier cosa que implemente el protocolo UIResponder como:
if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}
Todo el crédito a @Phil_M
Tal vez llego 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; \
})
Solución más rápida
extension UIView {
var parentViewController: UIViewController? {
for responder in sequence(first: self, next: { $0.next }) {
if let viewController = responder as? UIViewController {
return viewController
}
}
return nil
}
}
sequence
bit). Entonces, si "rápido" significa "más funcional", entonces supongo que es más rápido.
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)
}
Versión Swift 4
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{
}
return
palabra clave ahora 🤓Solución 1:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}
Solución 2:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
A la respuesta de Phil:
En línea: id nextResponder = [self nextResponder];
si self (UIView) no es una subvista de la vista ViewController, si conoce la jerarquía de self (UIView) puede usar también: id nextResponder = [[self superview] nextResponder];
...
Mi solución probablemente se consideraría un poco falsa, pero tuve una situación similar a la de mayoneez (quería cambiar de vista en respuesta a un gesto en un EAGLView), y obtuve el controlador de vista del EAGL de esta manera:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;
EAGLViewController *vc = [(EAGLAppDelegate *)[UIApplication sharedApplication].delegate viewController];
.
ClassName *object
, con un asterisco.
Creo que hay un caso en que lo observado necesita informar al observador.
Veo un problema similar en el que el UIView en un UIViewController está respondiendo a una situación y necesita primero decirle a su controlador de vista principal que oculte el botón Atrás y luego, al finalizar, decirle al controlador de vista principal que necesita salir de la pila.
He estado intentando esto con delegados sin éxito.
No entiendo por qué esto debería ser una mala idea?
Otra forma 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 ahí es donde el controlador puede establecerse en la propiedad. Básicamente, es en lugar de buscar (con un poco de pirateo) el controlador, tener el controlador configurado para la vista; esto es simple pero tiene sentido porque es el controlador el que "controla" la vista.
Si no va a subir esto a la App Store, también puede usar un método privado de UIView.
@interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
@end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];
var parentViewController: UIViewController? {
let s = sequence(first: self) { $0.next }
return s.compactMap { $0 as? UIViewController }.first
}
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 requiere ver la clase de controladores.
USO:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];
No hay manera
Lo que hago es pasar el puntero UIViewController a UIView (o una herencia apropiada). Lo siento, no puedo ayudar con el enfoque del problema de IB porque no creo en IB.
Para responder al primer comentarista: a veces necesita saber quién lo llamó porque determina lo que puede hacer. Por ejemplo, con una base de datos, es posible que solo tenga acceso de lectura o lectura / escritura ...