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.
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).
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
.
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
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.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With