美文网首页iOS技术交流iOS知识积累tableview&&
iOS自适应cell行高的那点破事儿

iOS自适应cell行高的那点破事儿

作者: PURE蓝胖子 | 来源:发表于2017-08-07 12:01 被阅读3880次
    =-=.jpeg
    FSAutoAdjust-cellHeightDemo

    前言

    其实早就准备写这篇文章了,但是一直没有系统去整理一下相关的demo,加上最近离职了,各种事情忙的有点郁闷,所以一直拖沓了下来。回家休息了一段时间想起来写了一半的demo,在还没找工作的这段空挡时间抽空完善了一下再写篇说明文档备忘一下。

    需求背景

    iOS的cell行高自适应是个非常常见的需求,也是一个非常简单的需求,之前我遇到过很多小伙伴不知道怎么来实现,在这里就一步步的来分析一下,供大家参考。

    问题分析

    其他的实现场景就不说了,我们现在来分析一下具体的需求,如图所示:


    cell行高自适应.png

    其实主要实现这几点就可以解决所谓的自适应行高的问题,下面我们就来逐步实现这个需求。

    计算UITableViewCell的高度

    说到计算高度,大家都不陌生,最简单常见的就是计算出每个子视图的高度累积起来返回我们所需要的cell高度,然后在UITableViewDelegate中调用:

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
       return 666;
    }
    

    或者高度固定的情况下直接

    self.tableView.rowHeight = 666;
    

    但是这就要求我们需要提前拿到model中的数据来手动计算每个控件的高度,这样既麻烦又不能通用,所以在autolayout出来之后我们只要给cellcontentView的上下左右都添加了约束,系统就可以自动的帮我们实现高度的自适应,就是一定要保证cell的高度可以被子视图撑开就可以了,利用的是systemLayoutSizeFittingSize这个API
    iOS8之后就更简单了,直接使用:

    self.tableView.estimatedRowHeight = 666;
    self.tableView.rowHeight = UITableViewAutomaticDimension;
    

    就可以了,其中estimatedRowHeight是预估高度,这里要注意delegate中的返回高度方法就不用在写了。
    关于这方面的文章,UITableView+FDTemplateLayoutCel的作者写的一篇文章十分详细,建议先去了解一下(优化UITableViewCell高度计算的那些事

    但是这个方法实际上在有多个子视图的cell上滑动是很卡顿的,特别是在iOS8尤其是iOS10上卡顿尤为明显,这跟系统的算高机制有一定关系,具体可以看上面的文章,这里不再解释了。
    如果脱离开autolayout来说,平时计算高度的话,最开始都是根据cell内子控件内容的高度来手动累加起来,但是这个方法每次都要去手动处理其中的算高逻辑,而且横竖屏切换的时候还要重新计算,在平时开发中就会浪费大量不必要的精力。所以后来我在项目中是通过调用layoutSubviews来获取到子控件的实际frame,这样就可以得到我们所需的cell高度值,如下代码所示:

    cell.frame = CGRectSetWidth(cell.frame, contentViewWidth);
        cell.contentView.frame = CGRectSetWidth(cell.contentView.frame, CGRectGetWidth(tableView.frame));
        [cell layoutIfNeeded];
        
        UIView *cellBottomView = nil;
        if (cell.FS_cellBottomView) {
            cellBottomView = cell.FS_cellBottomView;
        }else if (cell.FS_cellBottomViews && cell.FS_cellBottomViews.count > 0) {
            cellBottomView = cell.FS_cellBottomViews[0];
            for (UIView *view in cell.FS_cellBottomViews) {
                if (CGRectGetMaxY(view.frame) > CGRectGetMaxY(cellBottomView.frame)) {
                    cellBottomView = view;
                }
            }
        }else {
            NSArray *contentViewSubViews = cell.contentView.subviews;
            if (contentViewSubViews.count == 0) {
                cellBottomView = cell.contentView;
            }else{
                cellBottomView = contentViewSubViews[0];
                for (UIView *view in contentViewSubViews) {
                    if (CGRectGetMaxY(view.frame) > CGRectGetMaxY(cellBottomView.frame)) {
                        cellBottomView = view;
                    }
                }
            }
        }
        
        CGFloat cellHeight = CGRectGetMaxY(cellBottomView.frame) + bottomOffset;
    

    其中的cellBottomView是位于cell最底部的子视图,为了提高计算效率最好传入,如果不确定哪个子视图在最下面,可以传入一个视图数组contentViewSubViews,详细使用方式可以查看demo。

    缓存cell高度

    高度计算出来后,正常来说我们的需求已经达到了,但是如果这个高度值每次滑动的时候由于cell的复用机制都会重新计算,若果这个cell的自定义样式很复杂,子视图太多,那么大量的计算一定会损耗性能而导致明显的卡顿,所以缓存机制就是个必要的措施,更何况苹果也建议这样做;
    demo提供了两个计算行高的API

    /**
     cell自动计算行高
    
     @param tableView tableView
     @param indexPath indexPath
     @param contentViewWidth cell内容宽度,不确定可传0
     @return cell高度
     */
    + (CGFloat)FSCellHeightForTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath cellContentViewWidth:(CGFloat)contentViewWidth bottomOffset:(CGFloat)bottomOffset;
    
    /**
     cell自动计算行高优化版
    
     @param tableView tableView
     @param indexPath indexPath
     @param cacheKey 当前cell唯一标识符
     @param contentViewWidth cell内容宽度,不确定可传0
     @return cell高度
     */
    + (CGFloat)FSCellHeightForTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath cacheKey:(NSString *)cacheKey cellContentViewWidth:(CGFloat)contentViewWidth bottomOffset:(CGFloat)bottomOffset;
    

    第一种使用数组来做缓存,传入对应cellindexPath作为数组索引值;第二种则采用字典来缓存数据,要求传入一个唯一标识符cacheKey来区分;
    两种方式都可以准确获得cell高度,第一种实现更简洁,缺点就是数据源发生变化时,所有的缓存就会清空重新计算后缓存,比如reloadData的时候;第二种就是在前者的基础上添加一个区分不同cell的标识符,使用时还是建议使用第二种,不会清空缓存数据,轻量级页面没什么区别。总之两种方法都做了缓存数据的容错处理,支持以下方法:

    @selector(reloadData),
            @selector(insertSections:withRowAnimation:),
            @selector(deleteSections:withRowAnimation:),
            @selector(reloadSections:withRowAnimation:),
            @selector(moveSection:toSection:),
            @selector(insertRowsAtIndexPaths:withRowAnimation:),
            @selector(deleteRowsAtIndexPaths:withRowAnimation:),
            @selector(reloadRowsAtIndexPaths:withRowAnimation:),
            @selector(moveRowAtIndexPath:toIndexPath:)
    

    兼容横竖屏

    这个需求实现较为简单,就是横屏和竖屏分别采用两套缓存数据,互不影响,切换横竖屏的时候自动切换数据源。

    - (NSMutableArray *)indexCacheArrForCurrentOrientation
    {
        return UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation) ? self.indexCacheArr_Portrait: self.indexCacheArr_Landscape;
    }
    

    最后实现的效果如图所示:

    FSAutoAdjust-cellHeightDemo.gif

    总之答题思路就是这些,使用方便,感兴趣的可以移步下载demo查看:FSAutoAdjust-cellHeightDemo

    相关文章

      网友评论

      • 叶小合:在cell的创建里,没有设置控件的宽和高,请问在类别里是怎么根据bottomView来计算高度的
      • 叶小合:数据的宽和高在哪计算的,我怎么没看出来
      • 跳跳虾:看过你的demo后感觉方式挺好的,但是有个疑问:
        1.在dele,reload等方法中没有对字典缓存进行操作吧?
      • 陈藩:如果cell 里面有像微信的一样的九宫格图片,其他的各种不同的样式,也可以这么做么?
        PURE蓝胖子:@陈藩 当然是可以的
      • 面具猴:如果里面加入一个自定义UIView还能行吗,我的怎么也显示不出来
      • 阿兹尔:网络图片 宽度固定 高度自适应 这个怎么搞。
      • sven7:亲 你的图片是网络图片还是本地图片呀
        mdiep:本地的图片
      • 帅气小提莫:你好,发现一个问题,你使用到了这个方法获取cell。UITableViewCell *cell = [tableView.dataSource tableView:tableView cellForRowAtIndexPath:indexPath];导致cell内的子视图一直在初始化创建,所以是不合理的。内存会一直增长。
        PURE蓝胖子:高度缓存下来后是不会再去调用的,所以不存在一直初始化创建这个讲法
        58fe5465d15f:哎,我以前也有这样获取cell,不知道还会这样

      本文标题:iOS自适应cell行高的那点破事儿

      本文链接:https://www.haomeiwen.com/subject/uxjdlxtx.html