Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where to put UITableViewCell logic?

For a while now I've had this dilemma on my mind. A cell in UITableView is essentially a view, thus the class for UITableViewCell should take care of view related things (i.e. presentation methods, layout and so on.) and have no business logic inside of it (usually taken care of the controller). But since we don't have a controller for each cell and only a controller for the whole table, I have trouble figuring out where to put my cell-wise logic. Putting it in the cell itself breaks MVC, but putting it in the table controller makes it hard to determine what cell the method is being called from (I prefer writing subclasses for my senders if the view is action based so I can add properties to help me determine what view this is).

For instance I have a cell, that cell has a UIButton inside of it, when the button is pushed a UIPopover appears. Now where do I put the popover presentation code (The presentation appears from one specific cell, therefore I must know which cell it's being called from.)

I'd like to know what other people do in this case and what are their best practices.

like image 840
carlossless Avatar asked Feb 17 '14 14:02

carlossless


3 Answers

  1. If you put the presentation of the popover inside the cell, then it's the best option. Why ?, because this is not logic, this is view related things and because the button who makes this action is inside your cell, then the code should be inside your cell (or you can send message(delegate) to your viewController to show that).

  2. Then what is the logic ? The logic is for example: calculating, date operations, sending things to server. All these should be inside another object that we can call it module or manager.

  3. The controller can exchange messages between all these objects (view - model), but the view and the module should be separated from each other.

Update: You may want to take a look at Single Responsibility principle

like image 63
Basheer_CAD Avatar answered Sep 28 '22 03:09

Basheer_CAD


Normally, it's to your View Controller to handle the "filling" logic for your cells. Cells are recipient that you fill each time.

It is even said in prepareForReuse: of UITableViewCell :

The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell.

So indeed, your cells shouldn't hold any logic other than displaying.

If you need logic like button in your cell, you should set a delegate (you create one protocol) to your subclass of UITableViewCell and then hold in your UIViewController the cell logic.

If you cell is unique, I recommend you to define your cell as a static cell (no reuse identifier). And make a strong link to it.

like image 45
Tancrede Chazallet Avatar answered Sep 28 '22 03:09

Tancrede Chazallet


You could subclass UITableView and UITableViewCell. Then, add delegate methods for the button. e.g. tableView:buttonWasPressedForCell: & buttonWasPressedForCell:. The tableView would conform to the cell's delegate and receive the message buttonWasPressedForCell:. Then, the tableView would send the message tableView:buttonWasPressedForCell: to it's delegate, in this case, your controller. This way you know which UITableView and which UITableViewCell the message was sent from.

Example:

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

Note: When you dequeue the cell in tableView:cellForRowAtIndexPath: or add the cell using Interface Builder be sure to set the cell's delegate to the tableView.

E.g.

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

    cell.delegate = tableView;

    return cell;
}
like image 40
Jonathan Avatar answered Sep 28 '22 04:09

Jonathan