一、前言
UITableView里面的cell复用是一个很不错的设计,原理是只创建可视
屏幕的UITableViewCell(正好手机屏幕上看到的内容),也可以说是所谓的“按需加载”,开始在屏幕上能看到多少条信息,就创建多少个UITableViewCell,然后如果用户滚动屏幕来查看下面的内容这样就会创建新的UITableViewCell。但是每次滚动都创建新的UITableViewCell显然不是最好的做法。
所以苹果公司的开发团队在此做了进一步的优化那就是利用dequeueReusableCellWithIdentifier来优化单元格。这个方法的具体实现是用一个ID标记给单元格,假如有20条信息也就是有20个单元格,每一屏显示10个,当往下(或往上)滚动的时候最上面的第一条信息如果被完全不显示了(也就是再屏幕以外了),就会把这个第一条信息的UITableViewCell放入到对象池里,然后下面的第11个信息只要有一点点显示出屏幕,就不再新创建UITableViewCell,而是去对象池里拿刚才的第一个信息用过的UITableViewCell给自己用。
二、重用过程
比如总共12个cell,屏幕上显示11个,所以打印indexpath.row
,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"%ld",(long)indexPath.row);
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kJLXProfileTableViewCell];
if (!cell) {
cell = [[ProfileTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:str];
}
cell.m_titleStr = [nameArray objectAtIndex:indexPath.row];
return cell;
}
当往上拉的时候,第12个漏出来,打印12,往下拉,按显示的先后顺序会打印
2017-03-10 13:49:53.617 xxx[95074:9138595] 12
2017-03-10 13:49:53.617 xxx[95074:9138595] 2
2017-03-10 13:49:55.478 xxx[95074:9138595] 1
2017-03-10 13:49:56.005 xxx[95074:9138595] 0
在屏幕一直显示的则不用重新加载。
三、重用遇到的问题-视图覆盖
重用相当于不用再重新创建UITableViewCell的对象,而是直接复用,从一个UITableViewCell对象池中获取一个以Identifier参数命名的UITableViewCell对象。
如果在资源紧缺的时候,这个池会自动清理多余的UITableViewCell对象,则可能无法返回对象,但如果资源丰富,则会保存一些UITableViewCell对象,在需要调用的时候迅速的返回,而不用创建。所以重取出来的cell是有可能已经捆绑过数据或者加过子视图的。
比如在UITableViewCell上面加一个TextView,或者其他子视图的时候,在复用的时候,快速的来回滚动太快,就会出现视图互相覆盖的现象,如果自定义cell中子视图太多,出现问题更频繁严重。
虽然做了限制,但是最后还是无奈,终于在找到了几个方案,统一说下
比如我重新定义的一个cell子类ProfileTableViewCell
,正常实现的方案
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = @"cellID";
ProfileTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[ProfileTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
}
cell.textLabel.text = @"Cell显示内容";
cell.detailTextLabel.text = @"Cell辅助显示内容";
return cell;
}
但是如果重用导致了内容覆盖、错乱的情况,可以试试下面的方案
3.1、复用之前清除子视图
可以在子类,比如ProfileTableViewCell中的这个函数
- (void)prepareForReuse
{
[super prepareForReuse];
}
中清除子视图。
也可以在
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ }
中清除子视图。
NSArray *subviews = [[NSArray alloc] initWithArray:cell.contentView.subviews];
for (UIView *subview in subviews) {
[subview removeFromSuperview];
}
注意,这个子视图是加载cell.contentView上的,如果是cell自己的imageView的话是不会清除的
比如设置了一个图片
[cell.imageView setImage:[UIImage imageNamed:@"home_data_custom"]];
那么重新加载清除这个图片的时候应该将他的内容置空,而不是上面的清除掉cell自己的imageView
if(cell.imageView.image)
{
[cell.imageView setImage:nil];
}
弊端:这个虽然解决了子视图覆盖的问题,但是清除了子视图也会是子视图暂存的内容丢失,比如textFeild中,一复用,里面已经输入过的内容就没了,所以输入之后就需要保存下来,再次加载的时候取值
3.2、取消cell的重用机制
通过indexPath来创建cell,不使用cell的重用机制也可以解决重复显示问题
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ProfileTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (!cell) {
cell = [[ProfileTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
}
cell.textLabel.text = @"Cell显示内容";
cell.detailTextLabel.text = @"Cell辅助显示内容";
return cell;
}
弊端:上面一样,其实也是每次都创建一次新的,也是子视图暂存内容就丢失了
3.3、为每个cell都创建一个标示用来单独复用
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *str= [NSString stringWithFormat:@"kJLXProfileTableViewCell%ld",(long)indexPath.row];
ProfileTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:str];
if (!cell) {
cell = [[ProfileTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
}
cell.textLabel.text = @"Cell显示内容";
cell.detailTextLabel.text = @"Cell辅助显示内容";
return cell;
}
弊端:就是每次去对应标示的cell复用,这样虽然占用内存大了
优点:就是子视图的内容不会丢
3.4、当页面拉动需要显示新数据的时候,把最后一个cell进行删除
当页面拉动需要显示新数据的时候,把最后一个cell进行删除,就有可以自定义cell,然后重新赋值
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = @"cellID";
ProfileTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[ProfileTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
}
else//当页面拉动的时候 当cell存在并且最后一个存在 把它进行删除就出来一个独特的cell我们在进行数据配置即可避免
{
while ([cell.contentView.subviews lastObject] != nil) {
[(UIView *)[cell.contentView.subviews lastObject] removeFromSuperview];
}
}
cell.textLabel.text = @"Cell显示内容";
cell.detailTextLabel.text = @"Cell辅助显示内容";
return cell;
}
弊端:删除之后需要对最后一个重新进行数据赋值,也就是说数据会丢失
优点:节省内存
四、总结
这几个方案都是主要应对在cell中自定义了视图,当重新复用的时候,有时候会导致视图的重复叠加和错乱。所以主要推荐后面两种,如果想简单,那么就是每个cell定义一个不同的标识,各自取各自的cell,如果想节省内存,那么就采取最后这个,然后重新赋值即可。
如果在cell中没有使用自定义视图,或者tableview的大小不足以滚动,只是显示在界面上并不滚动,不会调用dequeueReusableCellWithIdentifier
,那么就老老实实使用dequeueReusableCellWithIdentifier:CellIdentifier
即可
五、参考文章
- iOS开发之cell重用后内容错乱
- UITableViewCell的prepareForReuse方法
- UITableView中Cell重用机制导致Cell内容出错的解决办法
- iOS之TableViewCell重用机制避免重复显示问题
版权属于:东哥笔记 - DongGe.org
本文链接:https://dongge.org/blog/511.html
自2017年12月26日起,『转载以及大段采集进行后续编辑』须注明本文标题和链接!否则禁止所有转载和采集行为!