更改 UITableViewCell 中的对象也更改了重用 UITableViewCell 的对象

Posted

技术标签:

【中文标题】更改 UITableViewCell 中的对象也更改了重用 UITableViewCell 的对象【英文标题】:Changing an Object in UITableViewCell is also changing the Object of a reused UITableViewCell 【发布时间】:2011-02-08 19:22:01 【问题描述】:

代码已更新到工作版本。再次感谢您的帮助:)

大家好。我有一个 UITableViewController 设置为使用从笔尖加载的自定义单元格。这是我的 cellForRowAtIndexPath:

    // Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
    NSString *cellIdentifier = @"PeopleFilterTableViewCell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

    if (cell == nil) 
        [[NSBundle mainBundle] loadNibNamed:@"PeopleFilterTableViewCell" owner:self options:nil];
        cell = peopleFilterTableViewCell;
        self.peopleFilterTableViewCell = nil;
    

    cell.selectionStyle = UITableViewCellSelectionStyleNone;

    PeopleFilterTableViewCell* tableViewCell = (PeopleFilterTableViewCell *) cell;

    /* Set direct button name */
    Person* personAtRow = [directsToShow objectAtIndex:indexPath.row];
    [tableViewCell.directButton setTitle:personAtRow.name forState:UIControlStateNormal];

    /* Set direct head count */
    tableViewCell.headcountLabel.text = [NSString stringWithFormat:@"%d", personAtRow.totalHeadCount];

    UIImage* unselectedImage = [UIImage imageNamed:@"filterButton.png"];
    UIImage* selectedImage = [UIImage imageNamed:@"filterButtonClosed.png"];

    UIButton* newFilterButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    /* Set filter button image */
    if(personAtRow.filtered)
        [newFilterButton setSelected:YES];
     else 
        [newFilterButton setSelected:NO];
    
    tableViewCell.filterButton = newFilterButton;

    return cell;

这对我来说似乎工作得很好,但是在 /* 设置过滤器按钮图像 */ 注释之后的代码出现了一个问题。

过滤器按钮是我的自定义单元格 nib 中的 UIButton,它应该反映包含“Person”对象的模型数组的状态,这些对象具有一个可以切换以表示它们是否被过滤的字段。

我允许用户更新此模型对象的方式是通过我的***控制器上的自定义委托方法,每当用户单击过滤器按钮时,更新模型和按钮的状态,并另外更新一个 mapViewController,根据模型的状态显示一些数据:

- (void)updateViews:(id)sender 
    UIImage* unselectedImage = [UIImage imageNamed:@"filterButton.png"];
    UIImage* selectedImage = [UIImage imageNamed:@"filterButtonClosed.png"];

    int row = [self rowOfCellSubView:sender];

    Person* personToFilter = [self.directsToBeShown objectAtIndex:row];
    NSLog(@"Filtering person with corpId: %@, name: %@", personToFilter.corpId, personToFilter.name);
    if (personToFilter.filtered)
        //update button image
        [sender setImage:unselectedImage forState:UIControlStateNormal];
        [sender setSelected:NO];
        //add person back.
        Person* directFiltered = [self.directsToBeShown objectAtIndex:row];
        directFiltered.filtered = NO;
        NSLog(@"Filtering person with corpId: %@, name: %@, filtered: %d", directFiltered.corpId, directFiltered.name, directFiltered.filtered);

     else 
        //update button image
        [sender setImage:selectedImage forState:UIControlStateSelected];
        [sender setSelected:YES];
        //remove person.
        personToFilter.filtered = YES;

        NSLog(@"Filtering person with corpId: %@, name: %@, filtered: %d", personToFilter.corpId, personToFilter.name, personToFilter.filtered);
    

    [self updateSitesToShow];
    [self.mapViewController performSelectorOnMainThread:@selector(updateDisplay) withObject:nil waitUntilDone:NO];


我的问题与过滤器按钮的状态更新有关。当我的应用加载 tableview 时,一切看起来都很好。当我单击某个单元格行中的过滤器按钮时,按钮的状态会正确更新,并且我的模型对象也会正确更新,因为我看到了我最终要更新的 mapView 的预期行为。

但是,问题是当我单击一个单元格行中的 filterButton 然后向下滚动几行时,我注意到另一个单元格中的另一个过滤器按钮现在与我在上面单击几行的那个状态相同.如果我再次向上滚动,我单击“打开”的原始按钮现在似乎是“关闭”,但它下面的行现在显示为“打开”。当然,所有这些都会影响按钮的实际显示。按钮的实际状态是一致的并且工作正常。

我知道这个问题一定与我的单元格被重复使用的事实有关,而且我猜想不同的单元格行会引用相同的按钮。但是,我不知道这是如何发生的,因为我正在为每个单元格创建一个新的过滤器按钮,无论该单元格是否被重用,并将单元格的 filterButton 属性重置为新创建的对象。例如,请注意 headCount 属性的文本,它也是在单元格 nib 中定义的 UILabel,也被重新分配给每个单元格的新 String 对象,并且每行都正确显示。

我这几天一直在努力解决这个问题。任何帮助或建议都将不胜感激。

【问题讨论】:

【参考方案1】:

表格视图缓存它们的单元格以允许您重复使用它们,每当您调用 dequeueReusableCellWithIdentifier: 时,您都会这样做。您应该在 tableView:cellForRowAtIndexPath: 方法结束之前调用 setSelected: 以将按钮的状态与对应于当前行的 Person 实例的状态同步。

要考虑的另一件事是,每次返回单元格时都创建新的按钮实例非常浪费。考虑在 if 块中为每个单元格实例创建和配置按钮(设置标题、图像等)一次,您将从 nib 文件加载单元格。

【讨论】:

我同意您的建议,即在更改按钮的图像后更新按钮的状态,并更新了代码来做到这一点。不幸的是,问题仍然存在。我也同意你的表现建议,但现在这并不是我真正关心的主要问题。关于可能导致这种情况的任何其他想法?我也尝试过 [tableViewCell.filterButton setNeedsDisplay] 无济于事。 确保修改 updateViews 方法中的代码以删除任何修改按钮的调用。事实上,您可能想要更改该方法的名称,因为它应该只更新模型,而不是视图。 顺便说一下,我个人对此的看法是,在这种情况下使用按钮只会增加不必要的复杂性。考虑将单元格的附件类型更改为UITableViewCellAccessoryCheckmark 以指示是否过滤了一个人(无论这意味着什么;不清楚),并实现tableView:didSelectRowAtIndexPath: 以响应用户选择该行。这就是表格视图的工作原理。 非常感谢大家。我不知道这条黄金法则:P。所以确实问题在于改变 cellForRowAtIndex 路径中的结构。单独设置按钮的状态就可以了。 @jlehr 我同意我应该在 updateViews 方法中删除对我的按钮的视图更新,并且它现在已经很好地破解了。我认为最好将按钮的图像设置为笔尖中的状态,然后在我的方法中更改状态。 @jlehr 我也想过这样做,但我仍然不太熟悉附件视图以及如何自定义它。我的表格单元格中有相当多的内容,我宁愿让用户单击单元格中的按钮而不是单元格本身来进行检查,因为这对我来说似乎更直观。 (在这种情况下,滤镜按钮具有睁眼/闭眼图像)。顺便说一句,在这种情况下,过滤意味着在我的地图视图中显示/不显示特定人员的“站点”对象列表(实现注释协议)。【参考方案2】:

这是一个典型的问题,当您在出列后更改单元格视图结构时会发生这种情况,而您只能在分配/初始化阶段更改单元格结构。在 dequeue 或 alloc/init 之后,您只能自定义内容而不是结构。

在您的情况下,当从笔尖加载单元格(假设它是第 0 行)时,会创建内部子视图结构(如笔尖中定义的那样)并将 filterButton 实例分配给这些子视图之一。但是下面几行你创建了一个新的 UIButton 并用这个新的替换了 filterButton 实例,但真正的按钮子视图将保持不变!现在当你点击一个按钮时,当然会触发“真正的”按钮(即单元格视图层次结构中最初由 Nib 创建的按钮),调用回调并更改状态。

稍后,当您向上滚动此第 0 行单元格时,它会从屏幕中移除并加入队列,然后重新用于另一个单元格,例如第 9 行。此时,先前的单元格 row-0 将被重新用于单元格 row-9,但设置 filterButton 仍然没有效果,因为您继续使用最初由 Nib 为单元格 row-0 加载的原始按钮。当然,您会看到这些按钮状态在滚动期间被弄乱,因为它们每次都被队列机制以不同的方式重用(所以 row-0 --> row-9,后面的 row-0 --> row-8 等等) .

解决方法是简单地改变按钮状态:[self.filterButton setSelected:NO|YES] 并且不改变单元格视图内容。

因此,黄金法则是:永远不要在对单元格结构进行 dequed 后更改它。如果您需要更改结构,请使用不同的单元格 ID。当然,单元格的可定制性越高,重复使用它们的可能性就越大。

【讨论】:

以上是关于更改 UITableViewCell 中的对象也更改了重用 UITableViewCell 的对象的主要内容,如果未能解决你的问题,请参考以下文章

更改 UITableViewCell 中的删除附件视图

更改 uitableviewcell 中的自定义附件视图?

根据 `UITableViewCell` 中的 `UILabel` 文本更改 `UIView` 宽度

从另一个视图控制器单击 uitableviewcell 后,swift 3 更改 uitableviewcell?

更改 UItableViewCell 中的 UILabel 文本颜色

更改自定义 UITableViewCell iphone中的文本颜色