我有一个“联系人列表”表格视图,其中包含一个包含电子邮件按钮的“联系人”单元格,当点击该按钮时,应该向电子邮件编辑器显示该联系人的电子邮件地址。
将 UIButton 与该单元格的“联系人”实例相关联的最佳方法是什么?
我已经为我想到的两种方法创建了答案——但我并不真正觉得令人满意。你更喜欢哪个,或者更好,建议更好的!
我有一个“联系人列表”表格视图,其中包含一个包含电子邮件按钮的“联系人”单元格,当点击该按钮时,应该向电子邮件编辑器显示该联系人的电子邮件地址。
将 UIButton 与该单元格的“联系人”实例相关联的最佳方法是什么?
我已经为我想到的两种方法创建了答案——但我并不真正觉得令人满意。你更喜欢哪个,或者更好,建议更好的!
使单元格处理操作并调用自定义委托方法。
// 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 原则吗?
我最常看到它完成的方式是将标签分配给等于 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
}
另一种解决方案:
对我来说,这样的东西完美无缺,看起来非常优雅:
- (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,我需要承认最好的解决方案是使用委托模式,这里的其他人已经建议了。
看了这些回答,我想说说我的看法:
- (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
但需要计算任何其他信息。这是一个很酷的方法,但会说不是最好的。
// 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
),您可能希望直接发送信息,而无需稍后计算它们,就像以前的解决方案一样。
享受 ;)
我想我找到了一种有点快的新方法。告诉我你对此的看法。
class ButtonCell: UITableViewCell {
var buttonAction: ( () -> Void)?
func buttonPressed() {
self.buttonAction?()
}
}
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()
}
您还可以在此调用中传递数据。像细胞或储存在细胞内的东西。
问候,
亚历克斯
通过从按钮遍历单元格的视图层次结构来确定单元格,然后确定索引路径。
// 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];
}
对我来说感觉很笨拙。有点“嗯”……</p>
我会提供另一种方法,只是不分配活动目标,事件将遍历响应者链:
[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