问题 UITableViewCell在响应者链中跳过


我试图在a的子视图中触发事件 UITableViewCell,让它冒出响应链,并由自定义处理 UITableViewCell 子类。

基本上:

SomeView.m (这是一个子视图 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");
}

为了测试为什么这不起作用,我添加了 someAction: ViewController和ViewController上的方法是最终处理从表视图单元子视图冒泡的事件的方法,即使Cell应该处理它。我检查了Cell是否在响应者链上,并且我已经验证了在单元格上方和下方的响应者链上的任何视图都会响应该事件,如果它们实现了 someAction: 方法。

到底发生了什么事?

这是一个展示它的项目 https://github.com/keithnorm/ResponderChainTest 这是预期的行为吗?我没有找到任何文档说明UITableViewCell的处理方式与其他UIResponder不同。


3107
2018-01-24 07:30


起源

有点奇怪!我发现响应链是:ContentView->UITableViewCellContentView->UITableViewCellScrollView->TableCell->UITableViewWrapperView->UITableView->View->ViewController->UIWindow->UIApplication->AppDelegate。我发现了 UIResponder * res = sender ; while (res) { res = [res nextResponder] ; NSLog(@"%@", [res class]) ; }。我会关注这个问题。 - KudoCC
是的,它似乎应该有效,对吧?感谢您查看并确认它对您来说似乎很奇怪:) - Keith Norman
是的,我下载了你的项目,并期望它应该工作但失败了。我也很困惑为什么它会跳过它 TableCell。 - KudoCC
所以你的目标是只想在TableCell上触发customEventFired:方法。对? - Vaisakh
我希望在子视图中触发事件,但是由TableCell捕获。 - Keith Norman


答案:


该单元格似乎要求其表格视图以获得许可。要改变它,你当然可以覆盖

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
    return [self respondsToSelector:action];
}

斯威夫特3:

override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
    return self.responds(to: action)
}

8
2018-06-21 17:17



我可以确认这是有效的,虽然我不确定“单元格似乎要求其表格查看权限”是这种行为的正确解释。表格视图与它有什么关系?这是在某处记录的吗? - Ole Begemann
表视图询问它的委托-tableView:canPerformAction:forRowAtIndexPath:withSender:,但只有它们是copy:或paste::)。该部分记录在案。这就是为什么响应者链不能按预期工作的原因:单元格劫持动作让表视图委托执行菜单验证。 - nschmidt
有意思,谢谢你的回答!我更新了我的示例项目 github.com/keithnorm/ResponderChainTest 表明这确实有效。 - Keith Norman


我已经得出结论,这可能是一个错误或未记录的预期行为。无论如何,我最终通过在子视图中响应事件然后手动将消息传播到响应者链中来强行修复它。就像是:

- (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;
  }
}

我还更新了我的演示项目,以展示我如何使用这个“修复” https://github.com/keithnorm/ResponderChainTest

如果有其他人想到这一点,我仍然欢迎任何其他想法,但这是我现在所拥有的最好的。


1
2018-01-24 22:03



谢谢,工作就像一个魅力。 - Sebastian Boldt


您可以将View.m中的代码更改为

      [button addTarget:nil action:@selector(customEventFired:) forControlEvents:(1 << 24)];

      [button addTarget:cell action:@selector(customEventFired:) forControlEvents:(1 << 24)];

0
2018-01-24 08:41



我可以,如果子视图有一个对其父单元格的引用,但这也打破了我试图维持的解耦。一个子视图几乎不会知道它的超级视图IMO。 - Keith Norman


这样做

    @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

现在 customEventFired: 方法被调用


0
2018-01-24 07:58



谢谢,但我想要做的是专门利用响应链。我的单元格没有像你的例子那样直接访问按钮,我想保持这种方式,以保持这些对象分离。 Point是一个在子视图中触发的事件,理论上应该发送到superview。这就像JavaScript中的事件委托/冒泡。 - Keith Norman
哦,对不起,我没有下载你的代码,只是直接发布我的答案,所以我现在,你不要在contentView本身添加按钮,然后你得到响应者到单元格的子视图 - Shankar BS
看到更新的答案 - Shankar BS


我认为这是最简单的解决方案。如果您未指定目标,则事件将自动冒泡响应者链。

[[UIApplication sharedApplication]sendAction:@selector(customAction:) to:nil from:self forEvent:UIEventTypeTouches];

0
2018-06-03 13:43