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

對比兩次我們的結果卻大不一樣。有關幀率的問題等我研究后,在寫一篇文章來記錄下。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容