UITableViewCell高度自适应

前言

其实这个关于 UITableViewCell 高度自适应的文章在网上也有很多,这里推荐下 sunnyxx单身的一篇文章,本文也是根据他的思路自己琢磨出来的,并且这也是一个比较古老的话题了,正好最近在项目中也需要去做一些高度自适应的东西,在这里做一个记录,记录下在 Masonry 自动布局中的 Cell 高度自适应

Let's Start

整个高度自适应的核心就是这段代码

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

在这个代码中我们需要返回 cell 的高度,默认都是44,那么在这个方法中我们还要用到的一个核心代是:

[someView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

这个方法返回的是一个 CGSize 对象,我们需要用到的就是这个返回值的 height 值,这个方法会根据 someView 中的子视图的约束来计算出一个新的size,那么如果你的约束条件有冲突的话这个方法返回的值会有问题,这个方法中还需要传入一个参数,这个参数类型有两种:

  • UILayoutFittingCompressedSize

  • UILayoutFittingExpandedSize

根据自己的理解,传入第一种参数会得出一个大小适中的尺寸,那么传入第二种参数的话的到的值应该比第一种参数的值要大。

介绍完核心的思路后,我们来看一下大致的代码,首先是自定义的 cell

 @interface DemoCell : UITableViewCell

- (void)setDemoCellType:(NSString *)type info:(NSString *)info;

@end

@interface DemoCell ()

@property (nonatomic, strong) UILabel *typeLabel;
@property (nonatomic, strong) UILabel *infoLabel;

@end

@implementation DemoCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        [self commonInit];
    }
    
    return self;
}

#pragma mark - Public Mehods

- (void)setDemoCellType:(NSString *)type info:(NSString *)info
{
    self.typeLabel.text = type;
    self.infoLabel.text = info;
}

#pragma mark - Customized

- (void)commonInit
{
    [self.contentView addSubview:self.typeLabel];
    [self.typeLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        
        make.top.equalTo(self.contentView.mas_top).offset(16);
        make.left.equalTo(self.contentView.mas_left).offset(16);
        make.width.mas_equalTo(100);
        
    }];
    
    [self.contentView addSubview:self.infoLabel];
    [self.infoLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        
        make.top.equalTo(self.typeLabel.mas_bottom).offset(10);
        make.left.equalTo(self.typeLabel.mas_left);
        make.right.equalTo(self.contentView.mas_right).offset(-16);
        make.bottom.equalTo(self.contentView.mas_bottom).offset(-5);
        
    }];
}

#pragma mark - Getters

- (UILabel *)typeLabel
{
    if (_typeLabel == nil)
    {
        _typeLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        _typeLabel.textColor = [UIColor blackColor];
        _typeLabel.backgroundColor = [UIColor redColor];
    }
    
    return _typeLabel;
}

- (UILabel *)infoLabel
{
    if (_infoLabel == nil)
    {
        _infoLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        _infoLabel.textColor = [UIColor blackColor];
        _infoLabel.backgroundColor = [UIColor yellowColor];
        _infoLabel.numberOfLines = 0;
    }
    
    return _infoLabel;
}

@end

这里代码都很简单,使用了 Masonry 布局,外部可调用 - (void)setDemoCellType:(NSString *)type info:(NSString *)info;来进行 cell 内容的赋值。

接下来就是 tableView 的代理方法中我们需要做的事情

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CGFloat height = 0;
    
    NSString *info = _dataArray[indexPath.row][kCellInfoText];
    NSString *type = _dataArray[indexPath.row][kCellTypeText];
    [self.demoCell setDemoCellType:type info:info];
     height = [self.demoCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    return height + 1;    
}

这里要注意的一点是 self.demoCell 只是用来计算的,它并没有显示在 tableView 上,还有这里是以 cell 中的 contentView 来计算,并且在最后返回的时候需要 +1 因为 cell 本身的高度要比它 contentView 的高度要高 1。

其实到这里,我们想要的效果就已经达到了,但是如果数据比较多并且很复杂的时候呢,这个方法会很耗时并且有可能会掉帧,掉帧的问题我现在还没有研究,主要说一下耗时,我的数据源大致长这个样子

    _dataArray = @[@{kCellInfoText:@"1\n2\n3\n4\n5\n6", kCellTypeText:@"type..."},
                   @{kCellInfoText:@"123456789012345678901234567890", kCellTypeText:@"type..."},
                   @{kCellInfoText:@"1\n2", kCellTypeText:@"type..."},
                   @{kCellInfoText:@"1\n2\n3", kCellTypeText:@"type..."},
                   @{kCellInfoText:@"1", kCellTypeText:@"type..."},
                   @{kCellInfoText:@"1\n2\n3\n4\n5\n6", kCellTypeText:@"type..."},
                   @{kCellInfoText:@"123456789012345678901234567890", kCellTypeText:@"type..."},
                   @{kCellInfoText:@"1\n2", kCellTypeText:@"type..."},
                   @{kCellInfoText:@"1\n2\n3", kCellTypeText:@"type..."},
                   @{kCellInfoText:@"1", kCellTypeText:@"type..."},
                   @{kCellInfoText:@"1\n2\n3\n4\n5\n6", kCellTypeText:@"type..."},....后面还有很多类似的就不贴出来了];

在说这个问题之前要先说下使用到的工具 Xcode 自带的 instrument 中的 Time Profiler 首先我们需要设置一下

设置

图中的勾选项依次是(参考自网络)

  • Separate by Thread: 显示每个线程

  • Invert Call Tree: 从上倒下跟踪堆栈,例如:FuncA{FunB{FunC}} 勾选此项后堆栈以C->B-A 把调用层级最深的C显示在最外面

  • Hide System Libraries: 勾选此项你会显示你app的代码,这是非常有用的. 因为通常你只关心cpu花在自己代码上的时间不是系统上的

还有

  • Flatten Recursion: 递归函数, 每个堆栈跟踪一个条目

  • Top Functions: 一个函数花费的时间直接在该函数中的总和,以及在函数调用该函数所花费的时间的总时间。因此,如果函数A调用B,那么A的时间报告在A花费的时间加上B花费的时间,这非常真有用,因为它可以让你每次下到调用堆栈时挑最大的时间数字,归零在你最耗时的方法。

那么接下来就可以开始测试了,测试数据如下:

测试1

当我们来回滑动的时候,它耗时会不断的在增加,因为每次滑动都会去计算一次高度,这显然不是我们想要的,为了解决这个问题,我们需要一个缓存来存储每一个 cell 的高度,那我们修改代码如下:

    if (self.cache[@(indexPath.row)])
    {
        height = [self.cache[@(indexPath.row)] floatValue];
    }
    else
    {
        NSString *info = _dataArray[indexPath.row][kCellInfoText];
        NSString *type = _dataArray[indexPath.row][kCellTypeText];
        [self.demoCell setDemoCellType:type info:info];
        height = [self.demoCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
        [self.cache setObject:@(height) forKey:@(indexPath.row)];
    }
    
    return height + 1;

我们将每个 cellindexPatch.row 作为 key 值来将高度存储到缓存中,第一次计算过后,每次都去缓存中去取。

测试2

对比两次我们的结果却大不一样。有关帧率的问题等我研究后,在写一篇文章来记录下。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容