2010-10-05 6 views
18

De acuerdo con la documentación de iOS, la cadena de respuesta se utiliza para pasar eventos táctiles "arriba de la cadena". También se usa para acciones generadas por controles. Multa.Caminando por la cadena de respuesta para pasar eventos personalizados. ¿Esto esta mal?

Lo que realmente me gustaría hacer es enviar una costumbre caso "en la cadena". El primer respondedor que elija el evento lo manejará. Esto parece un patrón bastante común, pero no puedo encontrar una buena explicación sobre cómo hacerlo en "iOS/Cocoa way".

Dado que la cadena de respuesta es exactamente lo que necesito, se le ocurrió una solución como esta:

// some event happened in my view that 
// I want to turn into a custom event and pass it "up": 

UIResponder *responder = [self nextResponder]; 

while (responder) { 

    if ([responder conformsToProtocol:@protocol(ItemSelectedDelegate)]) { 
     [responder itemSelected:someItem]; 
     break; 
    } 

    responder = [responder nextResponder]; 
} 

Esto funciona perfectamente, pero tengo la sensación de que no debe haber otras maneras de manejar esto. Caminar la cadena manualmente de esta manera no parece muy ... agradable.

Tenga en cuenta que las notificaciones no son una buena solución a este problema, porque yo sólo quiero que los objetos en la jerarquía de vistas a estar involucrados, y las notificaciones son globales.

Cuál es la mejor manera de manejar esto en iOS (y Cacao para el caso)?

EDITAR:

¿Qué deseo lograr?

Tengo un controlador de vista, que tiene una vista, que tiene subvistas, etc ... Varias de las subvistas son de un tipo específico que muestra un elemento de la base de datos. Cuando el usuario toca esta vista, se debe enviar una señal al controlador para navegar a una página de detalles de este elemento.

La vista que maneja el grifo es de varios niveles por debajo de la vista principal en la jerarquía de vistas. Tengo que decirle al controlador (o en algunos casos una subvista específica "arriba de la cadena") que se ha seleccionado un elemento.

Escucha de notificaciones sería una opción, pero no me gusta que la solución porque la selección de un artículo no es un evento global. Está estrictamente vinculado al controlador de vista actual.

+0

"... Tengo la sensación de que debe haber otras maneras de manejar esto ..." Puede haber, pero es difícil de decir sin saber exactamente por qué quiere pasar un evento personalizado en la cadena. ¿Cuál es tu objetivo final? –

+0

Vea mi explicación agregada en la pregunta. –

+0

Con respecto a esta pregunta de cinco años ... esta es la hermosa y moderna forma de Swift para hacer ciertas tareas como esta http://stackoverflow.com/a/37515358/294884 – Fattie

Respuesta

12

Eres bastante estrecha. ¿Qué sería más estándar es algo como esto:

@implementation NSResponder (MyViewController) 
- (void)itemSelected:(id)someItem 
{ 
    [[self nextResponder] itemSelected:someItem]; 
} 
@end 

Eso es por lo general cómo los acontecimientos van pasando por la cadena por defecto. Luego, en el controlador adecuado, anule ese método para realizar una acción personalizada en su lugar.

Puede que este no sea el patrón correcto para lo que quiere lograr, pero es una buena manera de pasar mensajes a la cadena de respuesta.

+0

Eso es asombroso. Tiene perfecto sentido, y sin duda ayudaría a resolver mi problema. ¿Este patrón se describe en alguna parte? –

+0

Que yo sepa. También me doy cuenta de que respondí en términos de Cocoa, pero todo debería ser aplicable a Cocoa Touch. –

+1

¿Por qué este patrón no está documentado en ninguna parte? –

13

UIApplication has a method for just this purpose, como lo hace its Cocoa cousin. Puede reemplazar todo ese código en su pregunta con un mensaje.

+1

He leído esto, y de alguna manera no me di cuenta la línea en la documentación que dice * "Si el objetivo es nulo, la aplicación envía el mensaje al primer respondedor, desde donde progresa hasta la cadena de respuesta hasta que se maneje". Muchas gracias por esta respuesta! ¡Muy útil! –

+1

Pregunta de seguimiento: Esta parece ser una gran solución, pero supone que siempre está trabajando con objetos UIEvent. Los objetos UIEvent tienen un alcance muy limitado (solo toques, gestos, etc.).¿Qué ocurre si quiero un evento personalizado de nivel superior como "elemento x seleccionado" o "elemento x eliminado"? Eso no funcionaría con "sendAction" –

+0

mientras que de hecho podría reemplazar todo el código con 1 mensaje, ¿pierde la capacidad de enviar algunos datos o parámetros a lo largo? Como sendAction: to: from: forEvent: no es compatible con esto, a menos que use incorrectamente el parámetro from o event. Puede tomar los datos del parámetro from usted mismo, pero me gustaría que los datos o el contexto estén agrupados e independientes. –

4

La solución de Peter funciona si está seguro de que el primer respondedor está configurado correctamente. Si desea más control sobre qué objeto se notifica sobre los eventos, debe usar targetForAction:withSender: en su lugar.

Esto le permite especificar la primera vista que debería ser capaz de manejar el evento, y desde allí recorrerá la cadena de respuesta hasta que encuentre un objeto que pueda manejar el mensaje.

Aquí es una función fully documented puede utilizar:

@interface ABCResponderChainHelper 
/*! 
Sends an action message identified by selector to a specified target's responder chain. 
@param action 
    A selector identifying an action method. See the remarks for information on the permitted selector forms. 
@param target 
    The object to receive the action message. If @p target cannot invoke the action, it passes the request up the responder chain. 
@param sender 
    The object that is sending the action message. 
@param userInfo 
    The user info for the action. This parameter may be @c nil. 
@return 
    @c YES if a responder object handled the action message, @c NO if no object in the responder chain handled the message. 
@remarks 
    This method pushes two parameters when calling the target. This design enables the action selector to be one of the following: 
@code 
- (void)action 
- (void)action:(id)sender 
- (void)action:(id)sender userInfo:(id)[email protected] 
*/ 
+ (BOOL)sendResponderChainAction:(SEL)action to:(UIResponder *)target from:(id)sender withUserInfo:(id)userInfo; 
@end 

Implementación:

@implementation ABCResponderChainHelper 
+ (BOOL)sendResponderChainAction:(SEL)action to:(UIResponder *)target from:(id)sender withUserInfo:(id)userInfo { 
    target = [target targetForAction:action withSender:sender]; 
    if (!target) { 
     return NO; 
    } 

    NSMethodSignature *signature = [target methodSignatureForSelector:action]; 
    const NSInteger hiddenArgumentCount = 2; // self and _cmd 
    NSInteger argumentCount = [signature numberOfArguments] - hiddenArgumentCount; 
    switch (argumentCount) { 
     case 0: 
      SuppressPerformSelectorLeakWarning([target performSelector:action]); 
      break; 
     case 1: 
      SuppressPerformSelectorLeakWarning([target performSelector:action withObject:sender]); 
      break; 
     case 2: 
      SuppressPerformSelectorLeakWarning([target performSelector:action withObject:sender withObject:userInfo]); 
      break; 
     default: 
      NSAssert(NO, @"Invalid number of arguments."); 
      break; 
    } 

    return YES; 
} 
@end 

Nota: Se utiliza el SuppressPerformSelectorLeakWarning macro.

Esto funciona muy bien en una UITableView. Imagine que tiene un reconocedor de gestos en la celda y necesita informar al controlador de vista de la acción que se realizó. En lugar de tener que crear un delegado para la celda y luego reenviar el mensaje al delegado, puede usar la cadena de respuesta en su lugar.

Ejemplo de uso (remitente):

// in table view cell class: 
- (void)longPressGesture:(UILongPressGestureRecognizer *)recognizer { 
    // ... 
    [ABCResponderChainHelper sendResponderChainAction:@selector(longPressCell:) to:self from:self withUserInfo:nil]; 
} 

Aquí self se refiere a la propia célula. El responderchain asegura que el método se envía primero al UITableViewCell, luego al UITableView, y finalmente al UIViewController.

Ejemplo de uso (receptor):

#pragma mark - Custom Table View Cell Responder Chain Messages 
- (void)longPressCell:(UITableViewCell *)sender { 
    // handle the long press 
} 

Si se trató de hacer lo mismo con sendAction:to:from:forEvent:, tiene dos problemas:

  • Con el fin de que funcione con la cadena de respuesta, debe pasar nil al parámetro to, en cuyo punto comenzará la mensajería en el primera respuesta en lugar de un objeto de su elección. Controlar el primera respuesta puede ser engorroso. Con este método, simplemente le dices en qué objeto comenzar.
  • No se puede pasar fácilmente un segundo argumento con datos arbitrarios. Usted would need to subclass UIEvent y agregue una propiedad como userInfo y páselo en el argumento del evento; una solución torpe en este caso.
Cuestiones relacionadas