Forget for a moment that you are using storyboard, and let's try to follow the best approach.
You have several solution, but for my opinion, just 1 of these is optimal: delegation pattern
.
First, you should extend your cell, and using delegation to return the cell when the user press the button. Then, you should use indexPathForCell
to get the indexPath
.
Let's see the approaches:
Cell by button position
- (void)buttonClicked:(id)sender
CGPoint buttonPosition = [sender convertPoint:CGPointZero
toView:self.tableView];
NSIndexPath *tappedIP = [self.tableView indexPathForRowAtPoint:buttonPosition];
// When necessary
// UITableViewCell *clickedCell = [self.tableView cellForRowAtIndexPath:tappedIP];
}
the solution above is certainly the most rapid to implement, but it is not the best from the point of view of the design/architecture. Moreover you get the indexPath
but you need then to calculate every other info. This is a cool method, but would say not the best.
Cell by while cycle on the button superviews
// 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];
}
Get the cell in this way is less performant of the former, and it is not elegant as well.
Cell by button tag
- (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
}
Absolutely no. Use the tag could seem a cool solution, but the tag of a control can be used for other reasons, like the next responder etc. I don't like this approach.
Cell by design pattern
// 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` ...
}
This is my favourite solution.
Using delegation
or blocks
is a really nice approach and you can pass all the parameters you want. In fact you could want to send back directly the needed info, without having to calculate them later.
Enjoy ;)