UITableView图文混排自动布局滑动优化实战

AutoLayout和手动计算高度

毫无疑问,使用AutoLayout会明显的比手动计算高度慢,那么我为什么要用AutoLayout呢,因为实在太方便了,而且视图太复杂,产品改的太频繁,手动计算实在工作量太大,维护起来超级麻烦。

而且新的技术出来了,不用不是亏了吗。

方案

1.缓存高度

既然手动计算高度更快,那就在Reuse的时候用AutoLayout帮我们算过后的高度就行了,缓存一个高度字典(或者数组),在算完渲染出来的时候取高度,在取高度的时候做个判断就行。

//保存高度
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
    [_cellHeightsDic setObject:@(cell.height) forKey:indexPath];
}
//设置高度
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
    NSNumber *height = [_cellHeightsDic objectForKey:indexPath];
    if (height) return height.doubleValue;
    return 400;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    NSNumber *height = [_cellHeightsDic objectForKey:indexPath];
    if (height) return height.doubleValue;
    return UITableViewAutomaticDimension;
}

2.图片和内容懒渲染

看不见的东西就不要让他渲染出来,这一步的优化是基于cellForRowAtIndexPath函数比willDisplayCell会先调用,如果在构造cell的时候就把所有内容填充上去,是一种浪费。因此可以把很重的内容,比如图片放到willDisplayCell的时候再加载。

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
    [_cellHeightsDic setObject:@(cell.height) forKey:indexPath];
    [cell willDisplay];
}
-(void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
    if ([tableView.indexPathsForVisibleRows indexOfObject:indexPath] == NSNotFound)
    {
        [(ABMainPageBaseTableViewCell*)cell endDisplay];
    }
}

然后在willDisplayendDisplay里做些特殊的处理,对于UIImageView就可以

-(void)endDisplay{
    [imageView setImage:nil];
    [imageView stopAnimating];
}

3.预加载

预加载分为两种,一种是图片预加载,另一种是内容预加载,先说内容预加载,其实就是在指定滚动到第几个cell的时候开始分页请求,这样用户就会无感知的开开心心的刷刷刷了。

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
    [_cellHeightsDic setObject:@(cell.height) forKey:indexPath];
    [cell willDisplay];
    if(_dataSource.count - indexPath.row < 6){
        //剩下五个内容就立马开始刷新
        [self loadMore];
    }
}

要注意的是控制好你的网络请求,保证一次只发起一次loadMore请求,不要重复加载了。

图片预加载,可以直接使用SDWebImagePrefetcher,下载图片

[[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:imgUrls];

会自动创建网络请求下载图片,下载完存入内存和本地缓存里,下次使用直接使用sd_setImageWithUrl会自动去内存里寻找下载完的图片。

4.GIF特殊处理

如果GIF太多了,做完以上优化,会发现滑动到GIF的时候还是很卡,原来是因为SDWebImage直接把下载完的GIF内容直接填充给UIImageView,会直接按帧把动画渲染出来,边滑动边渲染图片到UIImageView上,就会导致UI线程阻塞,用户就感觉到卡顿了。

于是尝试手动解GIF数据,使用第三方库FLAnimatedImage手动解GIF,在渲染的时候从内存读入缓存完的NSData,庆幸的是最新的SDWebImage已经支持了FLAnimatedImage,因此可以直接愉快的sd_setImage了。

最后要做的就是把滑动和GIF动画分开,想到的是NSRunLoop,因为滑动事件是在NSEventTrackingRunLoopMode下的,使用NSDefaultRunLoopMode就可以保证不在UI动画的时候取帧和渲染GIF。

直接设置FLAnimatedImageViewrunLoopMode即可。