美文网首页基础知识iOS开发实用技术iOS学习开发
iOS开发 | 简单实在的cell高度自适应内容及提前计算并缓存

iOS开发 | 简单实在的cell高度自适应内容及提前计算并缓存

作者: Lol刀妹 | 来源:发表于2017-05-24 11:03 被阅读6471次
    允儿

    一.使用约束(无需计算)

    原理:cell的子控件通过垂直方向的约束将cell撑开。
    比如下面这个tableView中的cell:


    每个cell由三部分构成:顶部一个30px红色view,中间一个高度自适应内容的label,底部一个30px的绿色view。
    如果我们想让cell高度自适应内容,可以这样设置约束:

    cell里UI搭建的代码:

    /** UI搭建 */
    - (void)setUpUI {
        //------- 红色view -------//
        UIView *redView = [[UIView alloc] init];
        [self.contentView addSubview:redView];
        redView.backgroundColor = [UIColor redColor];
        
        //------- label -------//
        self.label = [[UILabel alloc] init];
        [self.contentView addSubview:self.label];
        self.label.font = [UIFont systemFontOfSize:20];
        self.label.numberOfLines = 0;
        
        //------- 绿色view -------//
        UIView *greenView = [[UIView alloc] init];
        [self.contentView addSubview:greenView];
        greenView.backgroundColor = [UIColor greenColor];
        
        //------- 建立约束 -------//
        [redView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.top.mas_equalTo(self.contentView);
            make.height.mas_equalTo(30);
        }];
        
        [self.label mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.mas_equalTo(self.contentView);
            make.top.mas_equalTo(redView.mas_bottom);
        }];
        
        [greenView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.mas_equalTo(self.label.mas_bottom);
            make.left.right.mas_equalTo(self.contentView);
            make.height.mas_equalTo(30);
            //===== 底部对齐是撑开cell的关键点 =====//
            make.bottom.mas_equalTo(self.contentView);
        }];
    }
    

    注意:

    • label的宽确定、高不设置,可以实现label的高度自适应内容。
    • 绿色view和cell的底部对齐是撑开cell的关键所在。

    使用时注意:

    • 需要给cell高度估计值,可以随便给,但不能不给:
      tableView.estimatedRowHeight = 175;
    • 不需要写- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath方法,写了的话cell高度以此代理方法为准。

    二.使用frame(自己计算高度)

    关于cell的高度,你可以把它看做两个部分:

    1. 高度固定的部分
    2. 高度不固定的部分

    还是拿上面那个cell来说,它高度固定的部分是顶部红色view和底部绿色view;高度不固定的是label,而我们要计算的,就是高度不固定的那部分。

    如何计算高度?

    当然是在- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath里计算:

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        SimpleFrameModel *model = self.dataArray[indexPath.row];
        NSString *string = model.text;
        
        NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:string];
        
        NSRange allRange = [string rangeOfString:string];
        
        [attrStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:15.0] range:allRange];
        
        [attrStr addAttribute:NSForegroundColorAttributeName value:[UIColor darkGrayColor]range:allRange];
        
        CGFloat titleHeight;
        
        NSStringDrawingOptions options =  NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading;
        
        // 获取label的最大宽度
        CGRect rect = [attrStr boundingRectWithSize:CGSizeMake(SCREEN_WIDTH, CGFLOAT_MAX)options:options context:nil];
        
        titleHeight = ceilf(rect.size.height);
        
        return titleHeight + 60; // 动态高度 + 静态高度
      
    }
    

    cell的高度就是高度固定部分(红色view的高度+绿色view的高度)与高度不固定部分(label的高度)的和。

    三.如何提前计算高度?

    滚动tableView时cell高度那个代理会不停回调,所以需要优化。

    思路
    cell的高度由cell的内容决定,cell的内容由cell的model决定。也就是说,当我们获取到tableView的数据源时,其实就已经可以计算出所有cell的高度了

    1.将计算cell高度那段代码抽离出来

    我写了个cell的类方法来计算cell高度:

    /** 计算cell高度 */
    + (CGFloat)heightWithModel:(SimpleFrameModel *)model {
        //SimpleFrameModel *model = self.dataArray[indexPath.row];
        NSString *string = model.text;
        
        NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:string];
        
        NSRange allRange = [string rangeOfString:string];
        
        [attrStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:15.0] range:allRange];
        
        [attrStr addAttribute:NSForegroundColorAttributeName value:[UIColor darkGrayColor]range:allRange];
        
        CGFloat titleHeight;
        
        NSStringDrawingOptions options =  NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading;
        
        // 获取label的最大宽度
        CGRect rect = [attrStr boundingRectWithSize:CGSizeMake(SCREEN_WIDTH, CGFLOAT_MAX)options:options context:nil];
        
        titleHeight = ceilf(rect.size.height);
        
        return titleHeight + 60; // 动态高度 + 静态高度
    }
    

    2.在获取到tableView数据源时计算cell高度并放到一个数组

        //------- 数据源 -------//
        self.dataArray = [NSMutableArray array];
        //------- 缓存高度的数组 -------//
        self.heightArray = [NSMutableArray array];
        
        //------- 创建假数据 -------//
        NSString *text = @"内容越来越多";
        for (int i = 0; i < 100; i ++) {
            text = [text stringByAppendingString:@"越来越多"];
            SimpleFrameModel *model = [[SimpleFrameModel alloc] init];
            model.text = text;
            [self.dataArray addObject:model];
            
            // 计算cell高度
            CGFloat cellHeight = [SimpleFrameCell heightWithModel:model];
            // 将高度放进高度数组
            [self.heightArray addObject:[NSNumber numberWithFloat:cellHeight]];
        }
    

    3.需要cell高度时直接在高度数组里获取即可:

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        NSNumber *cellHeight = self.heightArray[indexPath.row];
        return cellHeight.floatValue;
    }
    

    四.总结

    实现cell高度提前计算只需一个数组及几行代码,逻辑也非常简单,然而带来的优化效果却是非常显著的,我自己测试的时候已深有感触。
    对于不只一个组的tableView,需要二维数组或字典,注意变通。

    五.详细demo

    代码详情见demo

    欢迎交流。


    2018年3月21日更新

    去掉了高度缓存数组,给model添加了一个cellHeight属性:

    @interface SimpleFrameModel : NSObject
    
    @property (nonatomic, copy) NSString *text;
    @property (nonatomic, assign) float cellHeight;
    
    @end
    

    拿到数据的时候就将高度算好:

    //------- 数据源 -------//
    self.dataArray = [NSMutableArray array];
    NSString *text = @"内容越来越多";
    for (int i = 0; i < 100; i ++) {
        text = [text stringByAppendingString:@"越来越多"];
        SimpleFrameModel *model = [[SimpleFrameModel alloc] init];
        model.text = text;
        // 计算高度,赋值给model
        model.cellHeight = [SimpleFrameCell heightWithModel:model];
        [self.dataArray addObject:model];
    }
    

    需要高度的时候直接从model中取:

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        SimpleFrameModel *model = self.dataArray[indexPath.row];
        return model.cellHeight;
    }
    

    相应代码已更新到github

    相关文章

      网友评论

        本文标题:iOS开发 | 简单实在的cell高度自适应内容及提前计算并缓存

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