iphone - puedo - Caminando por la cadena de respondedores para pasar eventos personalizados. ¿Esto esta mal?
sincronizar calendario iphone con google calendar (3)
Estas bastante cerca Lo que sería más estándar es algo como esto:
@implementation NSResponder (MyViewController)
- (void)itemSelected:(id)someItem
{
[[self nextResponder] itemSelected:someItem];
}
@end
Por lo general, así es como los eventos pasan la cadena de forma predeterminada. Luego, en el controlador correcto, anule ese método para realizar una acción personalizada.
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 respondedores.
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.
Lo que realmente me gustaría hacer es enviar un evento personalizado "hasta la cadena". El primer respondedor a recoger en el evento lo manejará. Esto parece ser un patrón bastante común, pero no puedo encontrar ninguna buena explicación sobre cómo hacerlo a "iOS / Cocoa".
Como la cadena de respuesta es exactamente lo que necesito, se me 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 debería haber otras formas 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 aquí, porque solo quiero que los objetos en la jerarquía de vistas estén involucrados, y las notificaciones son globales.
¿Cuál es la mejor manera de manejar esto en iOS (y Cocoa para esa materia)?
EDITAR :
¿Qué quiero 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 está varios niveles por debajo de la vista principal en la jerarquía de vistas. Tengo que decirle al controlador (o, en algunos casos, a una subvista específica "cadena arriba") que se seleccionó un elemento.
Escuchar las notificaciones sería una opción, pero no me gusta esa solución porque seleccionar un elemento no es un evento global. Está estrictamente vinculado al controlador de vista actual.
La solución de Peter funciona si está seguro de que la primera respuesta está configurada correctamente. Si desea más control sobre qué objeto se notifica sobre los eventos, debe usar targetForAction:withSender: lugar.
Esto le permite especificar la primera vista que debería ser capaz de manejar el evento, y desde allí rastreará la cadena de respondedores hasta que encuentre un objeto que pueda manejar el mensaje.
Aquí hay una función completamente documentada que puedes usar:
@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)userInfo@endcode
*/
+ (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: esto utiliza la macro SuppressPerformSelectorLeakWarning .
Esto funciona muy bien en un 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 respondedores en su lugar.
Uso de la muestra (remitente):
// in table view cell class:
- (void)longPressGesture:(UILongPressGestureRecognizer *)recognizer {
// ...
[ABCResponderChainHelper sendResponderChainAction:@selector(longPressCell:) to:self from:self withUserInfo:nil];
}
Aquí el self
refiere a la propia célula. La chain responder garantiza que el método se envíe primero a UITableViewCell
, luego a UITableView
y, finalmente, a UIViewController
.
Uso de la muestra (receptor):
#pragma mark - Custom Table View Cell Responder Chain Messages
- (void)longPressCell:(UITableViewCell *)sender {
// handle the long press
}
Si intentaste hacer lo mismo con sendAction:to:from:forEvent: tienes dos problemas:
- Para que funcione con la cadena de respondedores, debe pasar
nil
al parámetro a, momento en el que iniciará el mensaje en el primer respondedor en lugar de un objeto de su elección. Controlar al primer respondedor puede ser engorroso. Con este método, simplemente le dices a qué objeto comenzar. - No se puede pasar fácilmente un segundo argumento con datos arbitrarios. Necesitaría
UIEvent
subclaseUIEvent
y agregar una propiedad comouserInfo
y transmitirla en el argumento del evento; Una solución torpe en este caso.
La aplicación UIA tiene un método para este propósito , al igual que su primo Cocoa . Puede reemplazar todo ese código en su pregunta con un mensaje.