热门搜索 :
考研考公
您的当前位置:首页正文

UITableViewCell的高度自适应浅析

来源:东饰资讯网

开篇:在我们开发中经常会用到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);
            }
        }
    }
}

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

Top