views:

133

answers:

2

According to the iOS documentation, the responder chain is used to pass touch events "up the chain". It's also used for actions generated by controls. Fine.

What I really would like to do is send a custom event "up the chain". The first responder to pick up on the event will handle it. This seems like a pretty common pattern, but I can't find any good explanation on how to do it the "iOS/Cocoa way".

Since the responder chain is exactly what I need, I came up with a solution like this:

// 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];
}

This works perfectly, but I have a feeling that there should be other ways of handling this. Walking the chain manually this way doesn't seem very... nice.

Note that notifications are not a good solution here, because I only want the objects in the view hierarchy to be involved, and notifications are global.

What's the best way of handling this in iOS (and Cocoa for that matter)?

EDIT:

What do I want to accomplish?

I have a view controller, which has a view, which has subviews etc... Several of the subviews are of a specific type that show an item from the database. When the user taps this view, a signal should be sent to the controller to navigate to a detail page of this item.

The view that handles the tap is several levels below the main view in the view hierarchy. I have to tell the controller (or in some cases a specific subview "up the chain") that an item was selected.

Listening to notifications would be an option, but I don't like that solution because selecting an item is not a global event. It's strictly tied to the current view controller.

+3  A: 

You're pretty close. What would be more standard is something like this:

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

That's generally have events get passed up the chain by default. Then in the right controller, override that method to instead take a custom action.

This may not be the right pattern for what you want to achieve, but it is a good way to pass messages up the responder chain.

Mike Abdullah
That's pretty awesome. Makes perfect sense, and it would certainly help to solve my problem. Is this pattern described anywhere?
Philippe Leybaert
Not to my knowledge. Also I realise I answered in terms of Cocoa, but it should all be as applicable to Cocoa Touch.
Mike Abdullah
+3  A: 

UIApplication has a method for just this purpose, as does its Cocoa cousin. You can replace all of that code in your question with one message.

Peter Hosey
I have read this, and somehow I didn't notice the line in the documentation that says *"If target is nil, the application sends the message to the first responder, from whence it progresses up the responder chain until it is handled."*. Thanks a lot for this answer! Very helpful!
Philippe Leybaert
Followup question: This seems to be a great solution, but it assumes you're always working with UIEvent objects. UIEvent objects are very limited in scope (only touches, gestures, etc...). What if I want a higher-level custom event like "item x selected", or "item x deleted"? That wouldn't work with "sendAction"
Philippe Leybaert
@Peter Hosey: while you could indeed replace all the code with 1 message, you lose the ability to send some data or parameter along? As sendAction:to:from:forEvent: does not support this, unless you would misuse the from or event parameter. You could grab the data from the from parameter yourself, but I'd like the data or context to be bundled and independent.
Yannick Compernol