为 UITableViewCell 的附件视图使用自定义图像并让它响应 UITableViewDelegate

Posted

技术标签:

【中文标题】为 UITableViewCell 的附件视图使用自定义图像并让它响应 UITableViewDelegate【英文标题】:Using a custom image for a UITableViewCell's accessoryView and having it respond to UITableViewDelegate 【发布时间】:2010-10-26 13:07:35 【问题描述】:

我正在使用自定义绘制的 UITableViewCell,包括单元格的accessoryView。我对附件视图的设置是通过以下方式进行的:

UIImage *accessoryImage = [UIImage imageNamed:@"accessoryDisclosure.png"];
UIImageView *accImageView = [[UIImageView alloc] initWithImage:accessoryImage];
accImageView.userInteractionEnabled = YES;
[accImageView setFrame:CGRectMake(0, 0, 28.0, 28.0)];
self.accessoryView = accImageView;
[accImageView release];

此外,当初始化单元格时,使用initWithFrame:reuseIdentifier: 我确保设置了以下属性:

self.userInteractionEnabled = YES;

不幸的是,在我的 UITableViewDelegate 中,我的 tableView:accessoryButtonTappedForRowWithIndexPath: 方法(尝试重复 10 次)没有被触发。代表肯定接线正确。

可能缺少什么?

谢谢大家。

【问题讨论】:

【参考方案1】:

遗憾的是,除非您使用其中一种预定义类型时提供的内部按钮类型被点击,否则该方法不会被调用。要使用您自己的,您必须将附件创建为按钮或其他 UIControl 子类(我建议使用 -buttonWithType:UIButtonTypeCustom 并设置按钮图像的按钮,而不是使用 UIImageView)。

这是我在 Outpost 中使用的一些东西,它自定义了足够多的标准小部件(只是稍微匹配我们的蓝绿色),我最终创建了自己的 UITableViewController 中间子类来保存所有其他表视图使用的实用程序代码(它们现在是 OPTableViewController 的子类)。

首先,此函数使用我们的自定义图形返回一个新的详细信息披露按钮:

- (UIButton *) makeDetailDisclosureButton

    UIButton * button = [UIButton outpostDetailDisclosureButton];

[button addTarget: self
               action: @selector(accessoryButtonTapped:withEvent:)
     forControlEvents: UIControlEventTouchUpInside];

    return ( button );

完成后,按钮将调用此例程,然后为辅助按钮提供标准 UITableViewDelegate 例程:

- (void) accessoryButtonTapped: (UIControl *) button withEvent: (UIEvent *) event

    NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint: [[[event touchesForView: button] anyObject] locationInView: self.tableView]];
    if ( indexPath == nil )
        return;

    [self.tableView.delegate tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];

此函数通过从按钮提供的事件中获取触摸在表格视图中的位置并在表格视图中询问该行的索引路径来定位该行。

【讨论】:

谢谢吉姆。很遗憾,我花了 20 多分钟想知道为什么我不能用自定义 imageView 做到这一点。我刚刚在 Apple 的示例附件应用程序上看到了如何做到这一点。尽管您的答案得到了很好的解释和记录,所以我将其标记并保留下来。再次感谢。 :-) 吉姆,很好的答案。一个潜在的问题(至少在我这边) - 我必须添加以下行才能在按钮上注册触摸: button.userInteractionEnabled = YES; 对于其他正在查看此答案的人,您也可以在与该行相对应的按钮上放置一个标签(如果您有多个部分,则需要做一些数学运算)然后就可以了从函数中的按钮中拉出标签。我认为这可能比计算触摸要快一些。 这需要您硬编码self.tableView。如果您不知道哪个 tableview 包含该行怎么办? @RyanJM 我曾经认为做一个 hitTest 是多余的,标签就足够了。事实上,我在一些代码中使用了标签的想法。但是今天我遇到了用户可以添加新行的问题。这使用标签杀死了黑客。 Jim Dovey 建议的解决方案(如 Apple 的示例代码所示)是一种通用解决方案,适用于所有情况【参考方案2】:

我发现这个网站很有帮助: custom accessory view for your uitableview in iphone

简而言之,在cellForRowAtIndexPath:中使用这个:

UIImage *image = (checked) ? [UIImage imageNamed:@"checked.png"] : [UIImage imageNamed:@"unchecked.png"];

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
CGRect frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
button.frame = frame;
[button setBackgroundImage:image forState:UIControlStateNormal];

[button addTarget:self action:@selector(checkButtonTapped:event:)  forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor = [UIColor clearColor];
cell.accessoryView = button;

然后,实现这个方法:

- (void)checkButtonTapped:(id)sender event:(id)event

    NSSet *touches = [event allTouches];
    UITouch *touch = [touches anyObject];
    CGPoint currentTouchPosition = [touch locationInView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];

    if (indexPath != nil)
    
        [self tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];
    

【讨论】:

我会为此说 +1,因为这是 Apple 建议在其文档中的示例代码中执行的操作:developer.apple.com/library/ios/#samplecode/Accessory/Listings/… 设置框架对我来说是缺失的部分。你也可以只设置图片(而不是背景),只要你不想要任何文本。 @richarddas 的回答中的链接已断开。新链接:developer.apple.com/library/prerelease/ios/samplecode/Accessory/…【参考方案3】:

我的方法是创建一个UITableViewCell 子类,并在其中封装将调用常用UITableViewDelegate 方法的逻辑。

// CustomTableViewCell.h
@interface CustomTableViewCell : UITableViewCell

- (id)initForIdentifier:(NSString *)reuseIdentifier;

@end

// CustomTableViewCell.m
@implementation CustomTableViewCell

- (id)initForIdentifier:(NSString *)reuseIdentifier;

    // the subclass specifies style itself
    self = [super initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier];
    if (self) 
        // get the button elsewhere
        UIButton *accBtn = [ViewFactory createTableViewCellDisclosureButton];
        [accBtn addTarget: self
                   action: @selector(accessoryButtonTapped:withEvent:)
         forControlEvents: UIControlEventTouchUpInside];
        self.accessoryView = accBtn;
    
    return self;


#pragma mark - private

- (void)accessoryButtonTapped:(UIControl *)button withEvent:(UIEvent *)event

    UITableViewCell *cell = (UITableViewCell*)button.superview;
    UITableView *tableView = (UITableView*)cell.superview;
    NSIndexPath *indexPath = [tableView indexPathForCell:cell];
    [tableView.delegate tableView:tableView accessoryButtonTappedForRowWithIndexPath:indexPath];


@end

【讨论】:

这是最好的答案。但是button.superviewcell.superview[tableView.delegate tableView:...] 不够安全。【参考方案4】:

对 Jim Dovey 上述回答的扩展:

在 UITableView 中使用 UISearchBarController 时要小心。在这种情况下,您要检查self.searchDisplayController.active 并使用self.searchDisplayController.searchResultsTableView 而不是self.tableView。 否则,当 searchDisplayController 处于活动状态时,您会得到意想不到的结果,尤其是在滚动搜索结果时。

例如:

- (void) accessoryButtonTapped:(UIControl *)button withEvent:(UIEvent *)event

    UITableView* tableView = self.tableView;
    if(self.searchDisplayController.active)
        tableView = self.searchDisplayController.searchResultsTableView;

    NSIndexPath * indexPath = [tableView indexPathForRowAtPoint:[[[event touchesForView:button] anyObject] locationInView:tableView]];
    if(indexPath)
       [tableView.delegate tableView:tableView accessoryButtonTappedForRowWithIndexPath:indexPath];

【讨论】:

【参考方案5】:

    为按钮标签定义宏:

    #define AccessoryViewTagSinceValue 100000 // (AccessoryViewTagSinceValue * sections + rows) must be LE NSIntegerMax
    

    创建按钮并在创建单元格时设置cell.accessoryView

    UIButton *accessoryButton = [UIButton buttonWithType:UIButtonTypeContactAdd];
    accessoryButton.frame = CGRectMake(0, 0, 30, 30);
    [accessoryButton addTarget:self action:@selector(accessoryButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
    cell.accessoryView = accessoryButton;
    

    在 UITableViewDataSource 方法中通过 indexPath 设置 cell.accessoryView.tag -tableView:cellForRowAtIndexPath:

    cell.accessoryView.tag = indexPath.section * AccessoryViewTagSinceValue + indexPath.row;
    

    按钮的事件处理程序

    - (void) accessoryButtonTapped:(UIButton *)button 
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:button.tag % AccessoryViewTagSinceValue
                                                    inSection:button.tag / AccessoryViewTagSinceValue];
    
        [self.tableView.delegate tableView:self.tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
    
    

    实现 UITableViewDelegate 方法

    - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath 
        // do sth.
    
    

【讨论】:

除非绝对必要,否则任何人都不应使用tag,请查找其他解决方案。【参考方案6】:

当点击按钮时,您可以让它在 UITableViewCell 子类中调用以下方法

 -(void)buttonTapped
     // perform an UI updates for cell

     // grab the table view and notify it using the delegate
     UITableView *tableView = (UITableView *)self.superview;
     [tableView.delegate tableView:tableView accessoryButtonTappedForRowWithIndexPath:[tableView indexPathForCell:self]];

 

【讨论】:

【参考方案7】:

使用 yanchenko 方法,我必须添加:[accBtn setFrame:CGRectMake(0, 0, 20, 20)];

如果您使用 xib 文件自定义 tableCell,则不会调用 initWithStyle:reuseIdentifier:。

改为覆盖:

-(void)awakeFromNib

//Put your code here 

[super awakeFromNib];


【讨论】:

【参考方案8】:

您必须使用UIControl 来正确获取事件调度(例如UIButton)而不是简单的UIView/UIImageView

【讨论】:

【参考方案9】:

斯威夫特 5

此方法使用UIButton.tag 使用基本位移来存储 indexPath。只要您没有超过 65535 个部分或行,该方法就适用于 32 位和 64 位系统。

public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 

    let cell = tableView.dequeueReusableCell(withIdentifier: "cellId")
    let accessoryButton = UIButton(type: .custom)
    accessoryButton.setImage(UIImage(named: "imageName"), for: .normal)
    accessoryButton.sizeToFit()
    accessoryButton.addTarget(self, action: #selector(handleAccessoryButton(sender:)), for: .touchUpInside)

    let tag = (indexPath.section << 16) | indexPath.row
    accessoryButton.tag = tag
    cell?.accessoryView = accessoryButton



@objc func handleAccessoryButton(sender: UIButton) 
    let section = sender.tag >> 16
    let row = sender.tag & 0xFFFF
    // Do Stuff

【讨论】:

【参考方案10】:

从 iOS 3.2 开始,您可以避免使用此处其他人推荐的按钮,而是使用带有轻击手势识别器的 UIImageView。一定要启用用户交互,在 UIImageViews 中默认是关闭的。

【讨论】:

以上是关于为 UITableViewCell 的附件视图使用自定义图像并让它响应 UITableViewDelegate的主要内容,如果未能解决你的问题,请参考以下文章

如何将附件视图背景设置为与 UItableViewCell 中的内容视图相同的背景?

从 UITableViewCell 的附件视图中删除视图不起作用

如果通过附件视图添加到 UITableViewCell,UITextField 无法成为第一响应者

获取 UITableViewCell 详细附件视图的图像

iOS 15 - UITableViewCell 附件视图框架问题

UITableViewCell 附件视图按钮未在 ipad 纵向模式下显示