美文网首页iOS Developer
UITableViewCell的高度自适应浅析

UITableViewCell的高度自适应浅析

作者: 花果山松鼠 | 来源:发表于2018-05-22 14:12 被阅读185次

    开篇:在我们开发中经常会用到UITableView, TableView上面的cell有时候非常复杂,高度可变、结构可变等等。那么这个时候我们该如何去自适应cell的高度呢。

    一.通过frame去计算

    第一种大家比较容易想到的方法,就是由上往下依次计算控件的frame,再将最下面的控件frame的最大y值拿到,赋值给整个cell_height,得到整个cell的最终高度。

    简单布局的就可直接计算,但,如果复杂一些,结构多变,就需要另外创建一个frameModel来专门计算和存储cell的frame。这种情况逻辑复杂,也比较容易出错。

    需要创建两个模型:model(存储数据)、frameModel(存储cell上子view的frame)

    两者间的关系:frameModel是通过model来进行设置的,通过传入的model中的属性,来判断cell中子view显示或隐藏,从而一步一步得到所有的子view的frame值,最后,将cell的高度保存起来。

    // model.h
    @interface Model : NSObject
    
    @property (nonatomic, copy) NSString *name; //昵称
    @property (nonatomic, copy) NSString *icon; //头像
    
    @end
    
    // FrameModel.h
    @class Model;
    @interface FrameModel : NSObject
    
    /** 数据模型 */
    @property (nonatomic, strong) Model *model;
    /** 头像frame */
    @property (nonatomic, assign, readonly) CGRect iconViewF;
    /** 昵称frame */
    @property (nonatomic, assign, readonly) CGRect nameLabelF;
    
    /** cell的高度 */
    @property (nonatomic, assign, readonly) CGFloat cellHeight;
    
    // FrameModel.m
    - (void)setModel:(Model *)model {
        
        _model = model;
        
        CGFloat screen_Width = [UIScreen mainScreen].bounds.size.width;
        
        // cell的宽度
        CGFloat cellW = screen_Width;
    
        // cell的边框宽度
        CGFloat cellBorderW = 10;
        // cell之间的间距
        CGFloat cellBorderMargin = 15;
        
        // 1.头像
        CGFloat iconWH = 35;
        CGFloat iconX = cellBorderW;
        CGFloat iconY = cellBorderW;
        _iconViewF = CGRectMake(iconX, iconY, iconWH, iconWH);
        
        // 2.昵称
        CGFloat nameX = CGRectGetMaxX(self.iconViewF) + cellBorderW;
        CGFloat nameY = iconY;
        CGSize nameSize = [model.name sizeWithFont:[UIFont systemFontOfSize:15]];
        _nameLabelF = (CGRect){{nameX,nameY},nameSize};
        
        // 13.cell的宽度
        _cellHeight = CGRectGetMaxY(self.nameLabelF) + cellBorderMargin;
    }
    
    // cell.h
    @class FrameModel;
    @interface Cell : UITableViewCell
    
    + (Cell *)cellWithTableView:(UITableView *)tableView;
    /** frame模型 */
    @property (nonatomic, strong) FrameModel *frameModel;
    
    // cell.m
    - (void)setFrameModel:(FrameModel *)frameModel {
        
        _frameModel = frameModel;
        
        Model *model = frameModel.model;
        // 头像
        self.iconView.frame = frameModel.iconViewF;
        [self.iconView sd_setImageWithURL:[NSURL URLWithString:model.icon]];
        
        // 昵称
        self.nameLabel.frame = frameModel.nameLabelF;
        self.nameLabel.text = model.name;
    }
    

    这一步步走下来应该非常直观的。
    最后也就只有一步了,将拿到的数据进行model和frameModel之间的转换。

    /**
     *  将Model模型转为FrameModel模型
     */
    - (NSArray *)frameModelsWithStatuses:(NSArray *)models {
        
        NSMutableArray *frames = [NSMutableArray array];
        for (Model *model in models) {
            FrameModel *frame = [[FrameModel alloc] init];
            frame.model = model;
            [frames addObject:frame];
        }
        return frames;
    }
    

    接下来就是数据展示咯,将frameModel导入进cell里面就OK了。

    这其实是一种分离思路,将各个模块分离化,逻辑会清晰很多,对于复杂的数据和UI异常适合。

    额,其中有一个sizeWithFont,是我创建的一个分类,只是为了简化代码。

    // NSString+Extension.m
    - (CGSize)sizeWithFont:(UIFont *)font maxW:(CGFloat)maxW {
        NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
        attrs[NSFontAttributeName] = font;
        CGSize maxSize = CGSizeMake(maxW, MAXFLOAT);
        NSString *version = [UIDevice currentDevice].systemVersion;
        if ([version doubleValue] > 7.0) {
            return [self boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrs context:nil].size;
        }else {
            return [self sizeWithFont:font constrainedToSize:maxSize];
        }
    }
    
    - (CGSize)sizeWithFont:(UIFont *)font {
        return [self sizeWithFont:font maxW:MAXFLOAT];
    }
    

    二.通过自动布局自动适配(推荐使用)

    1.在cell中,setModel方法中所做的事情只是将数据导入view中进行展示,其他都不用管。label的话需要设定preferredMaxLayoutWidth属性。

    // cell.m
    - (void)setModel:(Model *)model {
        _model = model;
        
        // 头像
        [self.iconView sd_setImageWithURL:[NSURL URLWithString:model.icon]];
        // 昵称
        self.nameLabel.text = model.name;    
        CGFloat screen_Width = [UIScreen mainScreen].bounds.size.width;
        CGFloat width = screen_Width - 35 - 5; // label的宽度
        // 自动布局:设定label文字的最大宽度,这个宽度也可以通过外部进行传递,从而设定。
        self.nameLabel.preferredMaxLayoutWidth = width;
    }
    

    2.在获取cellHeight代理方法中,因为可能无法获取到当前的cell,我们的目的只是要得到cell的高度,所以在这我们创建了一个临时变量cell,用来计算cellHeight。

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
        self.cell.nameLabel.text = nil;
        self.cell.model = self.models[indexPath.row];
        // cell进行自动布局,可以得到cellHeight
        CGFloat cellHeight = [self.cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height+1;
        return cellHeight>31.0f? cellHeight:31.0f;
    }
    

    后记:如果有些需求是想得到tableview最大的高度,让cell完全展示出来,可以监听tableView的contentSize属性。
    注:iOS10以下,监听contentSize属性;iOS10及以上监听scrollView.contentSize属性。

    float iOS_version = [[[UIDevice currentDevice] systemVersion] floatValue];
    if (iOS_version >= 8.0 && iOS_version < 10.0) {
        [self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    }else if (iOS_version >= 10.0) {
        [self.tableView addObserver:self forKeyPath:@"scrollView.contentSize" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        
        float iOS_version = [[[UIDevice currentDevice] systemVersion] floatValue];
        if (iOS_version >= 8.0 && iOS_version < 10.0) {
            if ([keyPath isEqualToString:@"contentSize"] && object == self.tableView) {
                CGSize size = [[change objectForKey:@"new"] CGSizeValue];
                CGFloat height = size.height; //得到tableview的高度。
                if(self.viewHeightBlock){
                    self.viewHeightBlock(height);
                }
            }
        }else if (iOS_version >= 10.0) {
            if ([keyPath isEqualToString:@"scrollView.contentSize"] && object == self.tableView) {
                CGSize size = [[change objectForKey:@"new"] CGSizeValue];
                CGFloat height = size.height;
                if(self.viewHeightBlock){
                    self.viewHeightBlock(height);
                }
            }
        }
    }
    

    看到这里,或许你挥一挥衣袖,只留下了一个赞。😄

    相关文章

      网友评论

        本文标题:UITableViewCell的高度自适应浅析

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