UITableView insertSections: withRowAnimation: 导致请求所有表格单元格

7
我正在修改一些现有的笨重代码,该代码仅在任何更改时调用[tableView reloadData],以使用更具体的表格更新和插入/删除方法。然而,在这样做时,我遇到了一些非常糟糕的行为。以前,正如你所想象的那样,当表格加载时,它仅请求当前可见的行的单元格。当使用reloadData时,这是行为。现在,调用insertSections后,将请求所有单元格,可能会有数百个。这导致为每一行创建单元格,完全破坏可重用单元格队列,只是浪费时间。我必须做错了什么。更改很简单,代码结果只要求可见行:
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    // ... ensure it's the right key
    [tableView reloadData];
}

代码会导致tableView请求所有内容:
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
    // ... ensure it's the right key
    NSUInteger sectionCount = [self sectionCount];
    NSIndexSet *indices = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)];
    [tableView insertSections:indices withRowAnimation:UITableViewRowAnimationFade];
}

我可以切换回去,看行为是否发生变化。非常令人沮丧。有什么想法吗?
专门添加奖励以查看是否有更多的洞察力。
beginUpdates/endUpdates不会影响任何内容,我也不希望它这样做,这只是一个命令,没有任何额外的内容需要合并到单个更新中。
我认为这只是渴望动画的副作用。要让所有内容“滑入”,必须有所有内容进行渲染。游戏结束。

你是如何确定所有单元格都被同时请求了?没有任何一个被出列吗? - Pascal
我创建了一个小的测试项目,可以重现您观察到的行为。这确实很奇怪。不能仅仅是动画,因为如果指定UITableViewRowAnimationNone,则行为相同。如果在调用insertSections:withRowAnimation:时插入位置不在屏幕上,则不会出现问题。 - Ole Begemann
1个回答

6

看起来你正在告诉表格插入所有的部分,这基本上相当于重新加载。当你告诉表格需要加载一个部分时,它需要读取该部分中的所有项目。此外,你是在 beginUpdates/endUpdates 块之外执行操作,这意味着每次添加部分时,表格必须立即提交更改。如果你将所有内容都包含在 beginUpdates/endUpdates 中,它将暂停所有查询,直到完成,这将允许系统合并冗余查询并消除不必要的查询。

你应该只使用 insertSections 添加新部分。如果你这样做,那么 tableview 就不必查询所有未更改的部分的信息,即使它们移动位置。此外,如果部分内的元素发生变化,但部分本身没有发生变化,则应使用行版本的方法修改该部分内的特定行,仅这些行将被查询。

下面是一个示例(来自基于 CoreData 的应用程序),可以解释这个问题。由于你拥有自定义模型而不是 NSFetchedResultsController,因此你将不得不找出正在进行的操作类型,而不仅仅是检查系统传递给你的一堆常量。

//Calculate what needs to be changed     

[self.tableView beginUpdates];

for (i = 0 i < changeCount; i++) {
  type = changes[i]; 

  switch(type) {
    case NSFetchedResultsChangeInsert:
      [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                    withRowAnimation:UITableViewRowAnimationFade];
      break;

    case NSFetchedResultsChangeDelete:
      [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                    withRowAnimation:UITableViewRowAnimationFade];
      break;
  }
}

switch(type) {

  case NSFetchedResultsChangeInsert:
    [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                          withRowAnimation:UITableViewRowAnimationFade];
    break;

  case NSFetchedResultsChangeDelete:
    [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                          withRowAnimation:UITableViewRowAnimationFade];
    break;

  case NSFetchedResultsChangeUpdate:
    [self configureCell:(id)[tableView cellForRowAtIndexPath:indexPath]
            atIndexPath:indexPath];
    break;

  case NSFetchedResultsChangeMove:
    [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                          withRowAnimation:UITableViewRowAnimationFade];
    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section]
                  withRowAnimation:UITableViewRowAnimationFade];
    break;
  }
}

[self.tableView endUpdates];

额外的一点信息可能会有用,这是一个完全空的表格。我推迟了数据的加载以保持UI的响应性,因此它最初是空白的,并在从网络上获取部分时添加部分。因此,使用reloadData从没有部分到所有部分是可以正常工作的,但显式添加部分会导致混乱?奇怪。 - Nick Veys
不,这是预期的,并且为什么您需要使用beginUpdates/endUpdates调用来包装您正在进行的操作。如果没有这些内容,tableView必须假定您所做的每个修改都可能影响显示,这意味着它必须查询所有部分以计算哪些单元格可见,然后获取所有将可见的内容。当您执行beginUpdates时,tableView停止更新,直到您调用endUpdates,在此时它可以在查询所有中间状态的情况下一次性完成所有操作。 - Louis Gerbarg
不需要查询您的中间状态,我需要更多的咖啡。 - Louis Gerbarg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接