UITableViewCell 的重用问题。

我每个 cell 里面都有一个 button,我为 button 设置了一个点击响应动作 btn_tapped
btn_tapped 里面我把 sender 转换成 button 对象,并操作它:比如设置为隐藏。但是发现跟它共用同一个内存 button 也隐藏起来了。
有没有什么方法可以拿到 button 并且设置属性又不冲突的?

阅读 11k
4 个回答

在复用TableView的时候,一定要从数据源的角度出发

以咱们这个问题为例,比如,我们的tableView数据源是timelineArray,保存着一个timeline列表。假如Button是“赞”这个按钮的话,我们之所以要展示这个“赞”按钮,是因为当前这条timeline中的isLiked(是否被赞)属性为NO.所以这样写:

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

    NSDictionary* timeline = [self.timelines objectAtIndex:indexPath.row];
    BOOL isLiked = [[timeline objectForKey:@"isLiked"] boolValue];
    //tableViewCell的样式是和数据源有关的,所以我们是根据数据源来确定样式。为了阅读方便写成下面这样,其实是可以写成一行:cell.likeButton.hidden = !isLiked;
    if (isLiked) {           
        cell.likeButton.hidden = YES;
    }else{            
        cell.likeButton.hidden = NO;
    }
    //我们最好再给button再加一个tag,方便标识之后点击的时候确定点击了哪条timeline
    cell.likeButton.tag = indexPath.row;
    return cell;
}

因为在滑动tableview时,就是通过cellForRowAtIndexPath来复用cell的,所以数据源的改变,cell的样式也会改变。同时,我们进行任何操作时,本来就会更新数据源的最新状态,这是最正确的做法。

- (void)btn_tapped:(id)sender
{
    if(![sender isKindOfClass:[UIButton class]){
        return;
    }
    UIButton *btn = (UIButton*)sender;
    //通过tag来获取到timeline的index
    NSInteger row = btn.tag;

    //【1】立即隐藏该按钮;
    btn.hidden = YES;        
    //【2】标记数据源,例如保存这条消息已经被赞过了,方便接下来复用cell时识别
    NSMutableDictionary *timeline = [self.timelines objectAtIndex:row];
    [timeline setObject:[[NSNumber alloc] initWithBool:YES] forKey:@"isLiked"];
}

你应该在点击之后把是否显示按钮的状态存起来,再reload TableView, 在tablveiw获取cell的代理方法时通过你存的是否要显示的状态来显示或者隐藏按钮

因为没有具体的代码,所以以下是我觉得可以解决的方案。
猜测问题:cell的重用机制造成了公用内存。
解决问题应该从重用方式上考虑。(可以自己搜索如何解决重用,应该有3种方法)
主要的解决办法就是CellIdentifier定义成不同的。
--------
我写了一个小例子

原始状态:

图片描述

点击btn后的状态(隐藏一个btn)

图片描述

目录文件:

图片描述


table代码

主要代码在-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;如下:

cpp-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //定义不同的CellIdentifier
    NSString *CellIdentifier = [NSString stringWithFormat:@"cell%i%i",indexPath.section,indexPath.row];
    //绑定CellIdentifier
    BtnTableViewCell *cell = (BtnTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"BtnTableViewCell" owner:self options:nil];
        cell = [topLevelObjects objectAtIndex:0];
    }
    return cell;
}

cell中的代码:

BtnTableViewCell.h中:

cpp@interface BtnTableViewCell : UITableViewCell
@property (weak, nonatomic) IBOutlet UIButton *cellBtn;
@property (weak, nonatomic) IBOutlet UILabel *cellTitle;
@end

BtnTableViewCell.m文件代码:

cpp- (void)awakeFromNib {
    [self.cellBtn addTarget:self action:@selector(btn_tapped:) forControlEvents:UIControlEventTouchUpInside];
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];
}

- (void)btn_tapped:(id)sender
{
    UIButton *btn = sender;
    btn.hidden = YES;
}

这样实现的效果就可以点击一个btn,让其隐藏,而且还可以尽量减少内存的使用。
不知道是否解决你的问题,如有问题,请继续提问。

不知道为什么就是排不好版面了。

新手上路,请多包涵

一楼说得好。即然是复用,那就应该把他从脑子里列出来。它只是装载数据的容器,一切属性都应该取决于他所装载的数据。所以变动的时候首先想到的应该是变动它的数据源,然后再刷新容器。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题