在NSTableView中设置doubleAction后,如何获取双击的具体位置?
解决NSTableView双击单元格不同区域执行不同操作的问题
我之前也碰到过完全一样的需求——单元格里塞了好几个交互元素,双击不同区域得触发不同逻辑,光靠clickedRow确实顶不住。这里有两种靠谱的解决方案,你可以根据自己的cell复杂度来选:
方法一:在doubleAction里通过坐标判断点击区域
这种方法不用改动cell的结构,直接在你设置的doubleAction方法里做坐标转换和区域判断就行:
- 先拿到当前的双击事件:
- (void)tableViewDoubleClicked:(id)sender { NSEvent *currentEvent = [NSApp currentEvent]; if (!currentEvent) return;
- 把事件的窗口坐标转换成tableView的本地坐标:
NSPoint pointInTableView = [self.tableView convertPoint:currentEvent.locationInWindow fromView:nil];
- 确认点击的行和列(虽然
clickedRow也能拿到,但用坐标判断更准确,能避免一些边界情况):
NSInteger row = [self.tableView rowAtPoint:pointInTableView]; NSInteger column = [self.tableView columnAtPoint:pointInTableView]; if (row == -1 || column == -1) return;
- 获取对应的cell,注意把
makeIfNecessary设为NO,避免创建不在可视区域的cell:
NSTableCellView *cell = [self.tableView viewAtColumn:column row:row makeIfNecessary:NO]; if (!cell) return;
- 再把tableView的坐标转换成cell的本地坐标:
NSPoint pointInCell = [cell convertPoint:pointInTableView fromView:self.tableView];
- 最后判断这个点落在cell的哪个子视图范围内,执行对应操作:
比如你的cell里有个叫editButton的按钮和detailLabel的标签:
if (NSContainsRect(cell.editButton.frame, pointInCell)) { // 双击了编辑按钮区域,执行编辑逻辑 [self editRow:row]; } else if (NSContainsRect(cell.detailLabel.frame, pointInCell)) { // 双击了详情标签区域,执行查看详情逻辑 [self showDetailForRow:row]; } else { // 双击了cell的其他区域,执行默认逻辑 [self defaultActionForRow:row]; } }
方法二:给cell内的子视图单独添加双击手势
如果你的cell结构比较复杂,子视图多,上面的坐标判断会显得杂乱,这种情况下给每个需要响应双击的子视图单独加手势识别器会更清晰:
- 在自定义cell的
awakeFromNib或者初始化方法里,给子视图添加双击手势:
- (void)awakeFromNib { [super awakeFromNib]; // 给编辑按钮加双击手势 NSClickGestureRecognizer *editGesture = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(onEditDoubleClicked:)]; editGesture.numberOfClicksRequired = 2; [self.editButton addGestureRecognizer:editGesture]; // 给详情标签加双击手势 NSClickGestureRecognizer *detailGesture = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(onDetailDoubleClicked:)]; detailGesture.numberOfClicksRequired = 2; [self.detailLabel addGestureRecognizer:detailGesture]; }
- 在cell里实现手势的回调方法,然后通过delegate或者block把事件传递给tableView的控制器:
先定义一个cell的delegate协议:
@protocol CustomTableCellDelegate <NSObject> - (void)cellDidDoubleClickEdit:(CustomTableCell *)cell; - (void)cellDidDoubleClickDetail:(CustomTableCell *)cell; @end @interface CustomTableCell : NSTableCellView @property (weak, nonatomic) id<CustomTableCellDelegate> delegate; @end
然后在手势回调里调用delegate方法:
- (void)onEditDoubleClicked:(NSClickGestureRecognizer *)gesture { if ([self.delegate respondsToSelector:@selector(cellDidDoubleClickEdit:)]) { [self.delegate cellDidDoubleClickEdit:self]; } } - (void)onDetailDoubleClicked:(NSClickGestureRecognizer *)gesture { if ([self.delegate respondsToSelector:@selector(cellDidDoubleClickDetail:)]) { [self.delegate cellDidDoubleClickDetail:self]; } }
- 最后在tableView的数据源方法里给cell设置delegate,拿到对应的row:
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { CustomTableCell *cell = [tableView makeViewWithIdentifier:@"CustomCell" owner:self]; cell.delegate = self; // 其他cell配置逻辑 return cell; } // 实现delegate方法 - (void)cellDidDoubleClickEdit:(CustomTableCell *)cell { NSInteger row = [self.tableView rowForView:cell]; [self editRow:row]; } - (void)cellDidDoubleClickDetail:(CustomTableCell *)cell { NSInteger row = [self.tableView rowForView:cell]; [self showDetailForRow:row]; }
两种方法的对比
- 方法一:代码集中,适合简单cell,不需要改动cell结构,能快速实现。但cell子视图多的话,坐标判断会越来越繁琐,维护性差。
- 方法二:模块化程度高,每个子视图的逻辑独立,适合复杂cell结构,后期扩展和维护更方便,但需要额外的delegate/block逻辑,代码量稍多。
你可以根据自己的实际情况选,我个人更推荐第二种,尤其是cell交互元素多的时候,代码会清晰很多。
内容的提问来源于stack exchange,提问作者Timmmm
相关产品推荐
相关产品推荐

