UITableViewCell 在出队时重绘
Posted
技术标签:
【中文标题】UITableViewCell 在出队时重绘【英文标题】:UITableViewCell is redrawing on dequeue 【发布时间】:2015-05-06 04:10:29 【问题描述】:我的设置
我有一个UITableViewCell
,它位于我的主情节提要的UITableViewController
中。它填充了一些从 REST API 中提取的 JSON 数据,这将导致每个单元格的高度可变。 UIImageViews
、UILabels
有各种不同的高度和风格,想想 Instagram 风格。
我的问题
当我滚动到第 5 或第 6 个单元格,然后返回时,它们开始重绘和重叠,因此文本会混合,线条会重绘,等等。
我的尝试
这似乎是 SO 上的一个常见问题,所以我尝试了几个已发布的解决方案。看来我的问题可能与其他人面临的问题相同,也就是说,每次出队时我都会在我的单元格上调用addSubview
,但我尝试检查单元格是否已经存在。我在某个地方遇到了另一个帖子(对不起,我不记得在哪里),这表明因为我在情节提要中创建它,它已经初始化并且if ( !cell )
已经返回 false,所以我不知道如何防止它重绘。
当我尝试从情节提要中删除单元格并以编程方式创建它时,我收到一条错误消息,提示它找不到具有我的标识符 @"Cell"
的单元格。
我也尝试过有人在出队时删除所有子视图的解决方案,所以我使用了:
for ( UIView *view in cell.contentView.subviews )
if ([view isKindOfClass:[UIView class]])
[view removeFromSuperview];
它什么也没找到。
【问题讨论】:
最简单的解决方案是在情节提要中创建所有子视图,这样您就不必在代码中添加任何内容。如果要在代码中添加它们,请创建一个 UITableViewCell 子类,并在其 init 方法中添加子视图。在 cellForRowAtIndexPath 中添加子视图很少是最好的方法。 谢谢@rdelmar。当我不确定其中会包含哪些内容时,我该怎么做?我知道我会有一张图片和至少 2 个标签,但我可以有额外的标签或按钮。这就是你建议子类化它的原因吗? 如果您基于 indexPath 添加这些额外的视图(有些行有它们,有些不基于数据源),那么您确实需要在 cellForRow 中添加它们。如果您没有太多不同的单元格类型,另一种解决方案是在情节提要中制作多个单元格,然后根据数据源将相应的单元格出列。 谢谢,我会在早上试一试,然后回复您。 @rdelmar - 我睡不着,不得不试一试,最后让它工作了。我确信它可以改进,但 MVP 是我现在需要的。您可以将此作为答案发布,以便我检查一下吗? 【参考方案1】:@rdelmar 的评论是正确的。你不应该做你正在做的事。可能有效,但这是一种糟糕的形式,你不想养成坏习惯。
首先,利用面向对象的编程。单元格应该能够根据您要求它显示的数据进行自我配置。表格视图不应该设计单元格。
UITableViewCells 需要针对速度进行优化。创建和添加子视图是一个缓慢的过程。做一次没问题,但是单元格会被重用(系统优化),你应该重用第一次创建单元格时添加的现有视图。
例如,您可以隐藏不需要的子视图。您可能希望在 -prepareForReuse 中执行此操作。您可以在 -layoutSubviews 中移动它们。或者在-updateConstraints中改变子视图的位置。
通常您只想将要显示的数据从数据源(通常是视图控制器)传递给表格视图单元子类。让单元格完成显示工作。
【讨论】:
谢谢,@dave-batton。你在这两件事上都是正确的,1)rdelmar 是正确的,2)我需要确保我没有学习坏习惯。我确信在解决这个问题时我会养成很多坏习惯,但我会尽我所能去经历和重构并学习最佳实践。感谢您的帮助。【参考方案2】:当你在你的单元格出列后添加你的子视图时,给你的子视图一个标签。这样,当你将一个单元格出列时,你可以先用你的标签检查是否存在子视图,如果存在,在添加新视图之前将其删除:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
...
// try to dequeue a cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:<yourCellIdentifier>];
if( !cell )
// create a new cell if necessary
static int _myViewTag = 1000987 // give it a high int : low value are used by the system in cells
UIView *v = cell.contentView viewWithTag:_myViewTag];
if( v ) // subview with such tag already exists, so remove it.
[v removeFromSuperview];
// now add your new subview
[cell.contentView addSubview:<yourView>];
// adjust height of cell to your view.
...
【讨论】:
【参考方案3】:尝试在您的cell 类 中添加一个新方法 以将cell 重置为其默认样式,并在dequeueCell
之后调用此方法。
【讨论】:
谢谢@britto-thomas,您能详细说明一下吗? 只需将单元格中所有子视图的框架设置为方法中的默认样式即可。或者,如果您的单元格有不同的样式,请在自定义单元格类中添加不同的单元格设计方法,并在加载前根据需要调用它。 我遇到的一个问题是,当我尝试遍历子视图时,似乎什么都没有。有关我尝试运行的代码,请参阅我原始帖子的最后一部分。【参考方案4】:管理此问题的最有效方法是将UITableViewCell
子类化并将所有需要的Views
添加为属性。所以现在当一个单元格出现“回收”时,你知道在哪里可以找到旧视图,例如:
[cell.myTextLabel setText:@""];
aaa,你已经完成了。
UPDATE 如果您只有少量“类型”单元格,则创建子类是有意义的。为每个创建一个子类。它变得多么复杂取决于您的具体情况。但我已经做到了,发现它是最有效的方法。
UPDATE 2 或者您可以在情节提要中制作多个单元格,然后根据数据源将相应的单元格出列,保存所有编码。
【讨论】:
以上是关于UITableViewCell 在出队时重绘的主要内容,如果未能解决你的问题,请参考以下文章
JS优先队列排序。出队时,先找出优先级最高的元素,再按照先进先出出队。