I'm attempting to trigger an event in a subview of a UITableViewCell
, and let it bubble up the responder chain and be handled by a custom UITableViewCell
subclass.
Basically:
SomeView.m (which is a subview of the UITableViewCell
)
[self.button addTarget:nil action:@selector(someAction:) events:UIControlEventTouchUpInside]
SomeCustomCell.m
- (void)someAction:(id)sender {
NSLog(@"cool, the event bubbled up to the cell");
}
And to test why this wasn't working, I've added the someAction:
method on the ViewController and the ViewController is the one that ends up handling the event that bubbles up from the table view cell subview, even though the Cell should handle it. I've checked that the Cell is on the responder chain and I've verified that any views on the responder chain both above and below the cell will respond to the event if they implement the someAction:
method.
What the heck is going on here?
Here's a project that shows it https://github.com/keithnorm/ResponderChainTest Is this expected behavior somehow? I haven't found any documentation stating UITableViewCell's are treated any differently than other UIResponder's.
The cell seems to ask its table view for permission. To change that you can of course override
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return [self respondsToSelector:action];
}
Swift 3:
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return self.responds(to: action)
}
I've concluded that this is either a bug or undocumented intended behavior. At any rate, I ended up brute force fixing it by responding to the event in a subview and then manually propagating the message up the responder chain. Something like:
- (void)customEventFired:(id)sender {
UIResponder *nextResponder = self.nextResponder;
while (nextResponder) {
if ([nextResponder respondsToSelector:@selector(customEventFired:)]) {
[nextResponder performSelector:@selector(customEventFired:) withObject:sender];
break;
}
nextResponder = nextResponder.nextResponder;
}
}
I've also updated my demo project to show how I'm using this "fix" https://github.com/keithnorm/ResponderChainTest.
I still welcome any other ideas if anyone else figures this out, but this is the best I've got for now.
You can change the code in View.m as
[button addTarget:nil action:@selector(customEventFired:) forControlEvents:(1 << 24)];
to
[button addTarget:cell action:@selector(customEventFired:) forControlEvents:(1 << 24)];
do like this
@implementation ContentView
// uncomment this to see event caught by the cell's subview
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setTitle:@"Click" forState:UIControlStateNormal];
[button setBackgroundColor:[UIColor blueColor]];
[button addTarget:self action:@selector(customEventFired:) forControlEvents:UIControlEventTouchUpInside];
button.frame = CGRectMake(4, 5, 100, 44);
[self addSubview:button];
}
return self;
}
- (void)customEventFired:(id)sender
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Event Triggered in cell subview" message:@"so far so good..." delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil];
[alertView show];
}
@end
now customEventFired:
method get called
I think this one is the easiest solution. If you do not specify a target the event will automatically bubble up the responder chain.
[[UIApplication sharedApplication]sendAction:@selector(customAction:) to:nil from:self forEvent:UIEventTypeTouches];