Cell适配一些解决方案

cell固定高度及cell不同高度的估算和计算。

一,如果Tableview的cell都是固定高度的,那么直接在初始化的时候进行赋值高度:
tableView.rowHeight = xxx;
一般的方法都是在代理里边返回了cell的高度,如果代理返回了cell的高度那么当前tableView.rowHeight设置会无效。代理方法为:
– (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath;
此方法会调用多次,如果固定cell的高度,建议直接用rowHeight属性进行设置。
代理方法适合多中高度cell的高度返回。

二,如果cell的高度不同,是需要进行计算cell的高度,如果用cell是用frame进行布局的话,那么cellHeight就得根据cell中的子空间大小加上下边距进行计算。计算起来着实麻烦。
如果cell是autolayout进行布局的话,cell的contentView提供了计算方法cell.contentView systemLayoutSizeFittingSize;
前提是cell.contentView中的布局中的约束合理,上下支撑严谨,否则计算出来的cell高度会是0。示例:

#import "DemoTableViewCell.h"

#define ScreenFrame                         [[UIScreen mainScreen] bounds]
#define ScreenWidth                         [[UIScreen mainScreen] bounds].size.width
#define ScreenHeight                        [[UIScreen mainScreen] bounds].size.height

@interface DemoTableViewCell ()
@property (nonatomic,strong)UIImageView *imgView;
@property (nonatomic,strong)UILabel *contentLb;
@end
@implementation DemoTableViewCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if(self){
        [self.contentView addSubview:self.imgView];
        [self.contentView addSubview:self.contentLb];
        NSLayoutConstraint *cont_0 = [NSLayoutConstraint constraintWithItem:self.imgView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeTop multiplier:1.0 constant:10.0];
        NSLayoutConstraint *cont_1 = [NSLayoutConstraint constraintWithItem:self.imgView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0];
        NSLayoutConstraint *cont_2 = [NSLayoutConstraint constraintWithItem:self.imgView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:40.0];
        NSLayoutConstraint *cont_3 = [NSLayoutConstraint constraintWithItem:self.imgView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:40.0];
        NSArray *imgConstraints = [NSArray arrayWithObjects:cont_0,cont_1,cont_2,cont_3, nil];
        [self.contentView addConstraints:imgConstraints];
        //
        NSLayoutConstraint *cont_l_0 = [NSLayoutConstraint constraintWithItem:self.contentLb attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.imgView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:10.0];
        NSLayoutConstraint *cont_l_1 = [NSLayoutConstraint constraintWithItem:self.contentLb attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:10.0];
        NSLayoutConstraint *cont_l_2 = [NSLayoutConstraint constraintWithItem:self.contentLb attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeRight multiplier:1.0 constant:-10.0];
        NSLayoutConstraint *cont_l_3 = [NSLayoutConstraint constraintWithItem:self.contentLb attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.contentView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-10.0];
        NSArray *lbConstraints = [NSArray arrayWithObjects:cont_l_0,cont_l_1,cont_l_2,cont_l_3, nil];
        [self.contentView addConstraints:lbConstraints];
    }
    return self;
}
- (UIImageView *)imgView
{
    if(!_imgView){
        _imgView = [[UIImageView alloc]init];
        [_imgView setTranslatesAutoresizingMaskIntoConstraints:NO];
        _imgView.backgroundColor = [UIColor orangeColor];
    }
    return _imgView;
}
- (UILabel *)contentLb
{
    if(!_contentLb){
        _contentLb = [[UILabel alloc]init];
        [_contentLb setTranslatesAutoresizingMaskIntoConstraints:NO];
        _contentLb.font = [UIFont systemFontOfSize:16.0];
        _contentLb.preferredMaxLayoutWidth = ScreenWidth - 20;
        _contentLb.numberOfLines = 0;
    }
    return _contentLb;
}
- (void)settingCellWithString:(NSString *)str
{
    if(str != nil){
        self.contentLb.text = str;
    }
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    _tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
    _tableView.delegate = self;
    _tableView.dataSource = self;
    _tableView.rowHeight = UITableViewAutomaticDimension;
    _tableView.estimatedRowHeight = 200.0;
    [self.view addSubview:_tableView];
    _dataArray = [NSMutableArray arrayWithObjects:@"[[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain]",
                  @"陈:缘缘份份聚散 命运在转谁能预料或尚有明天黎:多少的恋爱是温暖",
                  @"陈:缘缘份份聚散 命运在转谁能预料或尚有明天黎:多少的恋爱是温暖陈:缘缘份份聚散 命运在转谁能预料或尚有明天黎:多少的恋爱是温暖",
                  @"陈:缘缘份份聚散 命运在转谁能预料或尚有明天黎:多少的恋爱是温暖陈:缘缘份份聚散 命运在转谁能预料或尚有明天黎:多少的恋爱是温暖陈:缘缘份份聚散 命运在转谁能预料或尚有明天黎:多少的恋爱是温暖",
                  @"陈:缘缘份份聚散 命运在转谁能预料或尚有明天黎:多少的恋爱是温暖陈:缘缘份份聚散 命运在转谁能预料或尚有明天黎:多少的恋爱是温暖陈:缘缘份份聚散 命运在转谁能预料或尚有明天黎:多少的恋爱是温暖陈:缘缘份份聚散 命运在转谁能预料或尚有明天黎:多少的恋爱是温暖",
                  @"多少的恋爱是温暖",
                  nil];
    [_tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return _dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *DemoTableViewCellID = @"DemoTableViewCellID";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:DemoTableViewCellID];
    if(cell == nil){
        cell = [[DemoTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:DemoTableViewCellID];
    }
    [(DemoTableViewCell *)cell settingCellWithString:_dataArray[indexPath.row]];
    return cell;
}

那么当前cell适配是做了高度自适应,但是如果数据量较大,在上下滑动的时候,每次系统都得去进行计算高度并且返回然后渲染,这样比较消耗系统资源,需将高度数据进行缓存。

三,AutoLayout布局的cell进行高度计算并且缓存高度。

缓存方案,通过cell方法systemLayoutSizeFittingSize计算高度,缓存到数据或者Model中,当前是在一个字典中。
首先删除初始化tableview中的rowHeight:
_tableView.rowHeight = UITableViewAutomaticDimension;
缓存如下,为tableview增加了一个分类,保存缓存数据:
在返回cell的代理方法中计算cell缓存,在返回高度时获取缓存。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CGFloat cellHeight = [tableView cacheHeightWithIndexPath:indexPath];
    NSLog(@"%lf",cellHeight);
    return cellHeight;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *DemoTableViewCellID = @"DemoTableViewCellID";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:DemoTableViewCellID];
    if(cell == nil){
        cell = [[DemoTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:DemoTableViewCellID];
    }
    [(DemoTableViewCell *)cell settingCellWithString:_dataArray[indexPath.row]];
    //进行缓存
    [tableView cacheHeightWithCell:cell contentWidth:self.view.bounds.size.width indexPath:indexPath];
    return cell;
}
@interface UITableView (CellHeight)
@property (nonatomic,strong,readonly)NSMutableDictionary *autoHeightDic;
//缓存高度
- (void)cacheHeightWithCell:(UITableViewCell *)cell contentWidth:(CGFloat)width indexPath:(NSIndexPath *)indexPath;
//获取高度
- (CGFloat)cacheHeightWithIndexPath:(NSIndexPath *)indexPath;
@end
//
#import "UITableView+CellHeight.h"
#import <objc/runtime.h>

@implementation UITableView (CellHeight)

- (void)setAutoHeightDic:(NSMutableDictionary * _Nonnull)autoHeightDic
{
    objc_setAssociatedObject(self, @selector(autoHeightDic), autoHeightDic, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSMutableDictionary *)autoHeightDic
{
    NSMutableDictionary *_autoHeightDic = objc_getAssociatedObject(self, @selector(autoHeightDic));
    if(_autoHeightDic == nil){
        _autoHeightDic = [NSMutableDictionary dictionary];
        objc_setAssociatedObject(self, @selector(autoHeightDic), _autoHeightDic, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return _autoHeightDic;
}
//缓存高度
- (void)cacheHeightWithCell:(UITableViewCell *)cell contentWidth:(CGFloat)width indexPath:(NSIndexPath *)indexPath;
{
    //设置字典key
    NSString *cacheKey = [self cacheKeyWithIndexPath:indexPath];
    //判断是否已经有缓存
    NSNumber *cacheHeight = [self.autoHeightDic objectForKey:cacheKey];
    if (cacheHeight) {
        return;
    }
    CGFloat height = [cell systemLayoutSizeFittingSize:CGSizeMake(width, 0) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel].height;
    cacheHeight = [NSNumber numberWithFloat:height];
    [self.autoHeightDic setValue:cacheHeight forKey:cacheKey];
}

//获取高度
- (CGFloat)cacheHeightWithIndexPath:(NSIndexPath *)indexPath
{
    NSString *cacheKey = [self cacheKeyWithIndexPath:indexPath];
    NSNumber *cacheHeight = [self.autoHeightDic objectForKey:cacheKey];
    if (cacheHeight) {
        return [cacheHeight floatValue];
    }
    return UITableViewAutomaticDimension;
}
- (NSString *)cacheKeyWithIndexPath:(NSIndexPath *)indexPath
{
    return [NSString stringWithFormat:@"%ld-%ld", indexPath.section, indexPath.row];
}

@end

比较复杂的cell,子控件比较多,每个子控件在计算整体cell的时候,也是需要再次进行计算高度,这样对CPU的资源消耗也是比较大的,所以需要可以将上述字典中的value作为一个model,保存每一个控件的高度作为缓存。

对于frame布局的cell,高度缓存可以在请求完数据进行缓存,通过请求完的数据,例如一个Model,去计算cell里边的动态视图和静态视图的高度,加起来则是整个cell的高度,然后进行缓存。参考:
https://www.jianshu.com/p/af4bc69839d8

如果有此tableview中有多种类型的cell,例如发送消息时,有文字冒泡cell,有图片cell,还有一些名片信息cell,每一种布局在代理方法中返回不同的cell,最好最缓存机制,否则会在刷新或者下拉加载时产生卡顿,跳屏的现象。
在autolayout布局计算cell高度时,必须确定cell中contentView的宽度。

Leave a Reply

Required fields are marked *