开篇:在我们开发中经常会用到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);
}
}
}
}
看到这里,或许你挥一挥衣袖,只留下了一个赞。😄
网友评论