添加单元格和使用 UITableView 滚动时出现图形故障

Posted

技术标签:

【中文标题】添加单元格和使用 UITableView 滚动时出现图形故障【英文标题】:Graphical glitches when adding cells and scrolling with UITableView 【发布时间】:2010-04-27 15:46:32 【问题描述】:

我正在使用 UITableView 来显示一系列计算的结果。当用户点击“计算”时,我将最新结果添加到屏幕上。当我添加一个新单元格时,UITableViewCell 对象被添加到一个数组中(由tableView:cellForRowAtIndexPath: 索引),然后我使用以下代码将此新行添加到屏幕上显示的内容中:

[thisView beginUpdates];
[thisView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation: UITableViewRowAnimationFade];
[thisView endUpdates];

这会导致显示新单元格。但是,然后我想立即向下滚动屏幕,以便新单元格是屏幕上最下方的单元格。我使用以下代码:

[thisView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

几乎效果很好。但是,第一次添加和滚动一个单元格时,它只会在屏幕上短暂出现,然后消失。视图向下滚动到正确的位置,但单元格不存在。手动滚动视图,直到这个不可见的新单元格的位置离开屏幕,然后再次返回,导致单元格出现 - 之后它的行为正常。这只发生在第一次添加单元格时;随后的单元格没有这个问题。无论scrollToRowAtIndexPathinsertRowsAtIndexPath 动画设置的组合如何,它也会发生。

编辑: 我现在开始在表格的倒数第二个位置插入单元格,而不是末尾,问题仍然存在 - 首次插入时,一个单元格是“不可见”的,直到它离开屏幕并返回再次开启。可能是什么原因造成的,如何强制将单元格添加到表格后立即绘制?

【问题讨论】:

【参考方案1】:

您遇到了问题,因为您更新了表而没有更新支持它的数据模型。表格实际上并不知道它们有多少行,也不知道要显示哪些单元格。他们依靠数据源和委托来告诉他们这些事情。您的设计希望表格本身能够跟踪它们。

insertRowsAtIndexPaths: 旨在用于在表中移动现有行,而不是用于添加全新的逻辑行。当您插入一个全新的单元格时,表格视图会丢失它实际拥有的行数。

在显示新行之前,您应该做的第一件事是更新返回的值:

– numberOfSectionsInTableView:
– tableView:numberOfRowsInSection:

... 以反映新行的添加。这将使表格了解它有多大。

然后您需要更新cellForRowAtIndexPath: 以返回添加行的正确单元格。然后你需要重新加载表。

完成此操作后,您应该能够将 tableview 滚动到最后并正确显示单元格。

关于表格要记住的重要一点是它们是愚蠢的。表本身不包含数据,不知道它有多少节和行,也不知道行和节的顺序。所有关于数据、节、行、单元格和单元格内容的逻辑都来自数据源和/或代表。当您想更改表格时,您实际上更改了数据源和/或委托,然后表格将自动反映这些更改。

编辑:

重读父项后,我发现您将 实际 UITableViewCell 对象 放入数据数组中,并且每一行都有一个单元格。

这不是 tableview 应该如何工作的,并且最多不会超过几十行

Tableview 旨在作为一种错觉,允许您显示具有任意高数或行数的逻辑表。为此,它只保留足够的 UITableViewCell 对象以覆盖 UI 中的视觉显示区域。默认单元格高度为 44 像素,这意味着 tableview 一次不会有超过 9 个单元格对象。

tableview 不是吃掉内存中保存未显示的单元格,而是让委托出列一个已滚动到屏幕外的单元格,用另一个 LOGICAL 行的数据重新填充它,然后将其显示在新位置。这是在cellForRowAtIndexPath:

您确实需要从这里重新开始您的设计。您的数据需要与用户界面对象分开。您不希望在任何时候都拥有比绝对必要更多的细胞,因为您的内存使用量会激增,并且您的响应时间会降低。您当前的问题是这种不寻常设计的结果。

完成后,您可以添加上述结果行。

【讨论】:

我的表的行保存在嵌套数组中。一个外部 NSMutableArray 包含每个部分的一个内部 NSMUtableArray。每个部分 NSMutableArray 都包含该部分的单元格。 numberOfSectionsInTableView: 返回节数组的数量,tableView:numberOfRowsInSection:' returns the number of cells in the relevant section array. cellForRowAtIndexPath` 使用节和行号来定位我的数组结构中的相关行,并返回。 我不希望有超过几行,更不用说几十行了。我会考虑重新设计,但我也想有一个短期的解决方案。 短期而言,您必须确保数据源在返回节数和行数时反映结果单元格的添加。将单元格添加到数据数组后,我会调用 -[UITableView reload]。这应该会导致 tableview 使用适当数量的部分和行来更新自身,这将让它正确显示自己。但是,这种设计总是会给你带来麻烦,而且总是会给你带来很多额外的工作。【参考方案2】:

在通过 NSTimer 或 performSelector:withDelay: 更新单元格后尝试滚动一段时间。它可以帮助解决我认为需要做更多工作的所有问题。

【讨论】:

这在以前有所帮助,但现在我将行插入到表格的中间并且没有任何区别。有什么建议吗?【参考方案3】:

故障可能是因为UITableView 认为自己是它正在显示的任何UITableViewCell 实例的所有者,并根据需要重用它们。该过程的一部分是在单元格上调用prepareForReuse。由于您将单元格保存在数组中,因此您不希望它们被重复使用。尝试在您的 UITableViewCell 类中实现一个空的 prepareForReuse。或者只是按照苹果的建议在tableView:cellForRowAtIndexPath: 中动态创建单元格。

【讨论】:

【参考方案4】:

我使用了 Skie 建议的方法来通过以下方式避免该问题:

添加行后立即:

[self performSelector:@selector(scrollToDesiredArea:) withObject:newIndexPath afterDelay:0.4f];

这称为以下内容:

-(void)scrollToDesiredArea:(NSIndexPath *)indexPath
     UITableView *thisView = (UITableView*)self.view;
     [thisView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

0.4s的延迟似乎足以避免毛刺;少一点,它仍然会发生。不过,在不同型号的 iPhone 硬件上可能会有所不同 - 我只在模拟器上进行了测试。

【讨论】:

当我只是在末尾添加一个单元格时,这工作得相当好。但是,现在我在其他单元格中间插入一个单元格,它只是无法绘制,直到它被滚动到屏幕外并返回。它下面的单元格向下滑动,留下一个空白空间,通过它我可以看到分组的表格背景。一旦这个空间被滚动到屏幕外并返回,它就会显示为我添加的单元格 - 从那时起就可以完美地工作了。

以上是关于添加单元格和使用 UITableView 滚动时出现图形故障的主要内容,如果未能解决你的问题,请参考以下文章

UITableView 滚动非常大的单元格

滚动 UITableView 和 UICollectionView 的数据不正确

iPhone-如何将 Storyboard 与自定义 UITableView 单元格和 CellWithIdentifier 一起使用

具有自定义单元格和多节数组数据的 Swift UITableView

UITableView 错误的图像加载了自定义单元格和 UIView

向 UITableViewCells 添加子视图会导致滚动时出现问题