【问题标题】:Where to put UITableViewCell logic?UITableViewCell 逻辑放在哪里?
【发布时间】:2014-03-16 22:33:39
【问题描述】:

一段时间以来,我一直在思考这个难题。 UITableView 中的单元格本质上是一个视图,因此 UITableViewCell 的类应该负责与视图相关的事情(即表示方法、布局等),并且其中没有业务逻辑(通常负责控制器)。但是由于我们没有每个单元格的控制器,而整个表只有一个控制器,所以我很难弄清楚将单元格逻辑放在哪里。将它放在单元格本身会破坏 MVC,但将其放在表控制器中会很难确定从哪个单元格调用该方法(如果视图是基于操作的,我更喜欢为我的发送者编写子类,因此我可以添加属性来帮助我确定这是什么观点)。

例如,我有一个单元格,该单元格内部有一个 UIButton,当按下按钮时会出现一个 UIPopover。现在我在哪里放置popover演示代码(演示出现在一个特定的单元格中,因此我必须知道它是从哪个单元格调用的。)

我想知道其他人在这种情况下做了什么以及他们的最佳做法是什么。

【问题讨论】:

  • 你可以继承 UITableView 和 UITableViewCell 然后为按钮添加一个委托方法。例如tableView:buttonWasPressedForCell: & buttonWasPressedForCell:. tableView 将符合单元格的委托并接收消息 buttonWasPressedForCell:。然后,tableView 会将消息 tableView:buttonWasPressedForCell: 发送给它的委托,在这种情况下,就是你的控制器。这样,您就知道消息是从哪个 UITableView 和哪个 UITableViewCell 发送的。
  • 好问题,真棒:)

标签: ios objective-c ipad uitableview


【解决方案1】:
  1. 如果您将弹出框的演示文稿放在单元格内,那么它是最佳选择。为什么?,因为这不是逻辑,这是与视图相关的事情,并且因为执行此操作的按钮在您的单元格内,那么代码应该在您的单元格内(或者您可以将消息(委托)发送到您的 viewController 以显示)。

  2. 那么逻辑是什么?逻辑例如:计算,日期操作,发送东西到服务器。所有这些都应该在另一个对象中,我们可以称之为modulemanager

  3. 控制器可以在所有这些对象之间交换消息(view - model),但是视图和模块应该彼此分开。

更新: 大家不妨看看Single Responsibility原理

【讨论】:

  • "Logic" 正在传递要显示的弹出框的数据,以及单元格子视图的操作回调。弹出框解除的回调也必须存在(我们在弹出框中做了一些事情并想要保存它)。因此,从单元格中呈现弹出窗口,然后使用保存您在弹出窗口中更改的数据的方法回调 tableview 控制器对我来说似乎是错误的。不过我可能有偏见。
  • 嗯,最好的方法是创建一个协议,并将tableViewController作为delegate,在按下按钮后,将消息传递给delegate,例如cellButtonWasPressed,而viewController应该决定做什么
  • 与@Jonathan 的评论相同。我开始认为这是要走的路。谢谢。
【解决方案2】:

通常,由您的视图控制器来处理单元格的“填充”逻辑。单元格是您每次填写的收件人。

甚至在UITableViewCellprepareForReuse:中说:

tableView:cellForRowAtIndexPath: 中的表格视图的委托在重用单元格时应始终重置所有内容。

确实,除了显示之外,您的单元格不应包含任何逻辑。

如果您在单元格中需要类似按钮的逻辑,您应该为UITableViewCell 的子类设置一个委托(您创建一个协议),然后在您的 UIViewController 中保存单元格逻辑。

如果您的单元格是唯一的,我建议您将您的单元格定义为静态单元格(无重用标识符)。并建立一个强有力的链接。

【讨论】:

    【解决方案3】:

    您可以继承 UITableViewUITableViewCell。然后,为按钮添加委托方法。例如tableView:buttonWasPressedForCell: & buttonWasPressedForCell:。 tableView 将符合单元格的委托并接收消息buttonWasPressedForCell:。然后,tableView 会将消息tableView:buttonWasPressedForCell: 发送给它的委托,在这种情况下,就是你的控制器。这样您就可以知道邮件是从哪个UITableView 和哪个UITableViewCell 发送的。

    示例:

    ABCTableView.h

    @protocol ABCTableViewDelegate <NSObject, UITableViewDelegate>
    
    // You may not need this delegate method in a different UIViewController.
    // So, lets set it to optional.
    @optional
    
    // Instead of passing the cell you could pass the index path.
    - (void)tableView:(ABCTableView *)tableView buttonWasPressedForCell:(ABCTableViewCell *)cell;
    
    @end
    
    
    @interface ABCTableView : UITableView
    
    // Declare the delegate as an IBOutlet to enable use with IB.
    @property (weak, nonatomic) IBOutlet id<ABCTableViewDelegate> delegate;
    
    @end
    

    ABCTableView.m

    @implementation ABCTableView
    
    @dynamic delegate;
    
    
    - (void)buttonWasPressedForCell:(ABCTableViewCell *)cell
    {
        // Check if the delegate responds to the selector since
        // the method is optional.
        if ([self.delegate respondsToSelector:@selector(tableView:buttonWasPressedForCell:)])
        {
            [self.delegate tableView:self buttonWasPressedForCell:cell];
        }
    }
    
    @end
    

    ABCTableViewCell.h

    @protocol ABCTableViewCellDelegate;
    
    
    @interface ABCTableViewCell : UITableViewCell
    
    // Declare the delegate as an IBOutlet to enable use with IB.
    @property (weak, nonatomic) IBOutlet id<ABCTableViewCellDelegate> delegate;
    
    @end
    
    
    @protocol ABCTableViewCellDelegate <NSObject>
    
    // You may not need this delegate method in a different custom UITableView.
    // So, lets set it to optional.
    @optional
    
    - (void)buttonWasPressedForCell:(ABCTableViewCell *)cell;
    
    @end
    

    ABCTableViewCell.m

    @implementation ABCTableViewCell
    
    - (IBAction)action:(id)sender
    {
        // Check if the delegate responds to the selector since
        // the method is optional.
        if ([self.delegate respondsToSelector:@selector(buttonWasPressedForCell:)])
        {
            [self.delegate buttonWasPressedForCell:self];
        }
    }
    
    @end
    

    注意: 当您将 tableView:cellForRowAtIndexPath: 中的单元格出列或使用 Interface Builder 添加单元格时,请务必将单元格的委托设置为 tableView。

    例如

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        ABCTableViewCell *cell = (ABCTableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"Cell"];
    
        cell.delegate = tableView;
    
        return cell;
    }
    

    【讨论】:

      【解决方案4】:

      通常对于这样的任务,我将我的 viewController 分配给单元格作为委托(并为其定义一些协议)。此外,我保持对填充单元格的对象的弱引用,所以在按钮的操作上,我将转发给委托(viewController)方法,如下所示:

      - (void)actionOnCell:(UITableViewCell *)cell fromView:(UIView *)sender withItem:(id)sourceItem;
      

      这样,我知道从哪里显示我的弹出框,以及其中显示了哪些信息(适合sourceItem)。

      编辑另外,如果单元格上有多个控件以避免重复非常相似的方法,您只需向上述函数添加一个参数,并定义所有可能操作的枚举

      【讨论】:

        【解决方案5】:

        为单元格创建一个动作处理程序和一个数据源。让您的数据源符合数据源协议(视图模型)。那么单元甚至不需要知道数据模型。

        界面中:TableViewCell

        @property (nonatomic, weak) id <SomeTableViewCellActionHandler> actionHandler;
        
        @protocol SomeTableViewCellActionHandler <NSObject>
        - (void)cell:(SomeTableViewCell *)cell didReceiveStartButtonAction:(UIButton *)button;
        - (void)cell:(SomeTableViewCell *)cell didReceivePauseButtonAction:(UIButton *)button;
        - (void)cell:(SomeTableViewCell *)cell didReceiveClearButtonAction:(UIButton *)button;
        @end
        

        实施

        - (void)prepareActionsForControls
        {
            [self.startButton addTarget:self action:@selector(handleStartButtonAction:) forControlEvents:UIControlEventTouchUpInside];
            [self.pauseButton addTarget:self action:@selector(handlePauseButtonAction:) forControlEvents:UIControlEventTouchUpInside];
            [self.clearButton addTarget:self action:@selector(handleClearButtonAction:) forControlEvents:UIControlEventTouchUpInside];
        }
        
        - (void)handleStartButtonAction:(id)sender
        {
            [self.actionHandler cell:self didReceiveStartButtonAction:sender];
        }
        
        - (void)handlePauseButtonAction:(id)sender
        {
            [self.actionHandler cell:self didReceivePauseButtonAction:sender];
        }
        
        - (void)handleClearButtonAction:(id)sender
        {
            [self.actionHandler cell:self didReceiveClearButtonAction:sender];
        }
        

        当您在视图控制器中创建单元格时 创建一个符合 MyTableViewCellActionHandler 协议的操作处理程序,如果需要进行演示,则将操作处理程序传递给 View Controller。

        cell.actionHandler = self.tableViewCellActionHandler;
        

        您还可以为您的单元格提供数据源并传入视图模型。 (MVVM) 这将允许您在单元格中仅保留表示代码,并将所有业务逻辑保留在它所属的位置。关注点分离。

        【讨论】: