最佳实践:处理来自表格视图单元格内按钮的操作?

Posted

技术标签:

【中文标题】最佳实践:处理来自表格视图单元格内按钮的操作?【英文标题】:Best practice: handling actions from buttons inside tableview cells? 【发布时间】:2013-04-01 14:29:26 【问题描述】:

我有一个“联系人列表”表格视图,其中包含“联系人”单元格,其中包含一个电子邮件按钮,当点击该按钮时,应向电子邮件撰写者显示该联系人的电子邮件地址。

将 UIButton 与该单元格的“联系人”实例相关联的最佳方法是什么?

我已经为想到的两种方法创建了答案——但我并不觉得这真的令人满意。您更喜欢哪个,或者更好,建议更好的!

【问题讨论】:

【参考方案1】:

方法二:

让单元格处理操作并调用自定义委托方法。

// YMContactCell.h
@protocol YMContactCellDelegate
- (void)contactCellEmailWasTapped:(YMContactCell*)cell;
@end

@interface YMContactCell
@property (weak, nonatomic) id<YMContactCellDelegate> delegate;
@end

// YMContactCell.m
- (IBAction)emailContact:(id)sender 
    [self.delegate contactCellEmailWasTapped:self];


// ContactListViewController.m
- (void)contactCellEmailWasTapped:(YMContactCell*)cell;
    NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
    YMContact *contact = [[self fetchedResultsController] objectAtIndexPath:indexPath];
    // present composer with `contact` ...

在视图中处理事件不违反 MVC 原则吗?

【讨论】:

我有一些和你一样,但我在 ViewCell.m 中的“IBAction”没有触发。 别忘了在ContactListViewController .m中设置cell.delegate=self 另外别忘了给接口添加代理:@interface ContactListViewController () &lt;YMContactCellDelegate&gt;【参考方案2】:

我最常看到的做法是将标签分配给与 indexPath.row 相同的按钮。

- (CustomCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 

    CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    cell.theLabel.text = self.theData[indexPath.row];
    cell.button.tag = indexPath.row;
    [cell.button addTarget:self action:@selector(doSomething:) forControlEvents:UIControlEventTouchUpInside];
    return cell;




-(void)doSomething:(UIButton *) sender 
    NSLog(@"%@",self.theData[sender.tag]);
    //sender.tag will be equal to indexPath.row

【讨论】:

如果有多个部分,这似乎是有限的。 @Samuel,它有多个部分,然后你只需用 tag = indexPath.section*100 + indexPath.row 之类的东西分配标签 @rdelmar 感谢您的建议。然而,这似乎很脆弱:如果一个部分有 100 多行怎么办?如果有其他观点,例如在来自开源库的自定义控件中决定使用完全相同的标签? @Yang,嗯,我不知道,用 * 1000 节代替呢?根据您的情况使用您需要的任何乘数。至于自定义控件,它是否具有相同的标签并不重要,因为它不会有相同的操作方法。只有你的按钮在调用 doSomething: 警告如果你有空部分!部分编号将是错误的【参考方案3】:

另一种解决方案:

对我来说,这样的东西完美无缺,看起来非常优雅:

- (void)buttonClicked:(id)sender
      CGPoint buttonPosition = [sender convertPoint:CGPointZero
                                       toView:self.tableView];
      NSIndexPath *clickedIP = [self.tableView indexPathForRowAtPoint:buttonPosition];

      // When necessary 
      // UITableViewCell *clickedCell = [self.tableView cellForRowAtIndexPath:clickedIP];

2017 年 12 月 1 日更新

一段时间后,实现了很多 UITableViews,我需要承认最好的解决方案是使用委托模式,这里其他人已经建议了。

【讨论】:

根据您的更新投票。感谢您的反馈【参考方案4】:

阅读这些答案,我想说一下我的看法:

按按钮位置的单元格

- (void)buttonClicked:(id)sender
  CGPoint buttonPosition = [sender convertPoint:CGPointZero
                                   toView:self.tableView];
  NSIndexPath *clickedIP = [self.tableView indexPathForRowAtPoint:buttonPosition];

  // When necessary 
  // UITableViewCell *clickedCell = [self.tableView cellForRowAtIndexPath:clickedIP];

上述解决方案无疑是实施最快的,但从设计/架构的角度来看并不是最好的。此外,您获得了indexPath,但需要计算任何其他信息。这是一种很酷的方法,但并不是最好的。

在按钮超级视图上按 while 循环

// ContactListViewController.m

- (IBAction)emailContact:(id)sender 
    YMContact *contact = [self contactFromContactButton:sender];
    // present composer with `contact`...


- (YMContact *)contactFromContactButton:(UIView *)contactButton 
    UIView *aSuperview = [contactButton superview];
    while (![aSuperview isKindOfClass:[UITableViewCell class]]) 
        aSuperview = [aSuperview superview];
    
    YMContactCell *cell = (id) aSuperview;
    NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
    return [[self fetchedResultsController] objectAtIndexPath:indexPath];

以这种方式获取单元格比以前更昂贵,也不优雅。

按按钮标记的单元格

- (CustomCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 

    CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    cell.theLabel.text = self.theData[indexPath.row];
    cell.button.tag = indexPath.row;
    [cell.button addTarget:self action:@selector(doSomething:) forControlEvents:UIControlEventTouchUpInside];
    return cell;


-(void)doSomething:(UIButton *) sender 
    NSLog(@"%@",self.theData[sender.tag]);
    //sender.tag will be equal to indexPath.row

绝对没有。使用标签似乎是一个很酷的解决方案,但是控件的标签可以用于很多事情,比如下一个响应者等。我不喜欢这个不是正确的方式

单元格设计模式

// YMContactCell.h
@protocol YMContactCellDelegate
- (void)contactCellEmailWasTapped:(YMContactCell*)cell;
@end

@interface YMContactCell
@property (weak, nonatomic) id<YMContactCellDelegate> delegate;
@end

// YMContactCell.m
- (IBAction)emailContact:(id)sender 
    [self.delegate contactCellEmailWasTapped:self];


// ContactListViewController.m
- (void)contactCellEmailWasTapped:(YMContactCell*)cell;
    NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
    YMContact *contact = [[self fetchedResultsController] objectAtIndexPath:indexPath];
    // present composer with `contact` ...

这就是解决方案。使用delegation 或使用blocks 确实是一件好事,因为您可以传递您想要的所有参数并使架构可扩展。事实上,在delegate 方法中(也可以使用blocks),您可以直接发送信息,而无需像之前的解决方案那样稍后计算它们。

享受;)

【讨论】:

感谢您对现有答案的看法。对于未来,请考虑使用评论来评论现有答案。【参考方案5】:

Swift 闭包方法

我想我找到了一种有点快的新方法。告诉我你的想法。

你的手机:

class ButtonCell: UITableViewCell 
    var buttonAction: ( () -> Void)?
    
    func buttonPressed() 
       self.buttonAction?()
   

你的 UITableViewDataSource:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell = tableView.dequeueReusableCell(withIdentifier: "CallCell", for: indexPath)
         //Handled inside 
         cell.buttonAction = 
            //Button pressed 
         
         //Handle in method
         cell.buttonAction = self.handleInnerCellButtonPress() 
 

您还可以在此调用中传递数据。比如细胞或者细胞里面储存的东西。

问候, 亚历克斯

【讨论】:

【参考方案6】:

方法一:

通过从按钮遍历单元格的视图层次结构来确定单元格,然后确定索引路径。

// ContactListViewController.m

- (IBAction)emailContact:(id)sender 
    YMContact *contact = [self contactFromContactButton:sender];
    // present composer with `contact`...


- (YMContact *)contactFromContactButton:(UIView *)contactButton 
    UIView *aSuperview = [contactButton superview];
    while (![aSuperview isKindOfClass:[UITableViewCell class]]) 
        aSuperview = [aSuperview superview];
    
    YMContactCell *cell = (id) aSuperview;
    NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
    return [[self fetchedResultsController] objectAtIndexPath:indexPath];

我觉得它很笨重。有点“嗯”……

【讨论】:

【参考方案7】:

我会提供另一种方法,只是不分配活动目标,事件将遍历响应者链:

[self.actionButton addTarget:nil action:@selector(onActionButtonClick:) forControlEvents:UIControlEventTouchUpInside];

然后,在您的视图控制器中:

- (void)onActorButtonClick:(id)sender 
  if ([sender isKindOfClass:UIButton.class]) 
    UITableViewCell *cell = [self findAncestorTableCell:(UIView *)sender]; //See other answer to fetch the cell instance.
    NSIndexPath *indexPath = [self.listTable indexPathForCell:cell];
    ...
  

但是,这会引发一些编译器警告,添加以下内容以忽略它们:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
...
#pragma clang diagnostic pop

【讨论】:

以上是关于最佳实践:处理来自表格视图单元格内按钮的操作?的主要内容,如果未能解决你的问题,请参考以下文章

swift中集合视图单元格内的按钮操作

信创办公–基于WPS的EXCEL最佳实践系列 (处理合并单元格)

当在同一个单元格内单击按钮时,如何在自定义表格视图单元格内隐藏/显示特定视图

Swift - 表格视图单元格内的单击按钮不会在该单元格的功能处调用确实选择行

信创办公--基于WPS的Word最佳实践系列(表格常见问题的处理)

在固定的 UILabel 中处理长文本的最佳实践是啥