0%

UITableViewCell 高度自适应

第三方框架

  • GitHub: UITableView-FDTemplateLayoutCell ⭐️8000+

✨方法一:使用 Auto Layout 并缓存高度

该方法参考:UITableView 自动计算 cell 高度并缓存,再也不用管高度啦
大致分 4 步实现:

第一步

自定义 UITableViewCell 子类对象,xib 文件中的控件使用 Auto Layout【自上而下】加好约束:

第二步

Controller 对象中添加一个 NSMutableDictionary 属性,用于保存缓存下来的 cell 高度值,这样可以解决点击状态栏不会自动滚动到顶部的问题。

@property (nonatomic, strong) NSMutableDictionary *heightAtIndexPath;

第三步

实现代理方法:-(void)tableView: willDisplayCell: forRowAtIndexPath:

用第 2️⃣步添加的 NSMutableDictionary 属性作为容器,在 cell 将要显示的时候在字典中保存这行 cell 的高度。

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

    NSNumber *height = @(cell.frame.size.height);
    [self.heightAtIndexPath setObject:height forKey:indexPath];

}

第四步

实现代理方法:-(CGFloat)tableView:estimatedHeightForRowAtIndexPath:

-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
    // 先去字典查看有没有缓存高度,有就返回,没有就返回一个大概高度
    NSNumber *height = [self.heightAtIndexPath objectForKey:indexPath];
    return (height ? height.floatValue : 100);
}

结果:
cell 的行高会自适应内容文本的高度:

✨方法二:使用默认 UITableViewCellStyleValue1 实现自适应高度

上面的方法是通过自定义 UITableViewCell 子类对象实现高度自适应的。
某天发现了一篇博文号称能使用系统默认 Cell 多行显示 detailTableView
参考:Multi-line UITableViewCell using UILabel

1️⃣设置 detailTextLabel.numberOfLines = 0 实现多行显示

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *cellReusreIdentifier = @"UITableViewCellStyleValue1";
    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:cellReusreIdentifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
                                      reuseIdentifier:cellReusreIdentifier];
    }

    NSArray *allkey = [self.dataSourceDictionary allKeys];
    NSString *oneKey = allkey[indexPath.row];

    cell.textLabel.text = oneKey;
    cell.detailTextLabel.text = self.dataSourceDictionary[oneKey];
    cell.detailTextLabel.numberOfLines = 0;
    cell.detailTextLabel.lineBreakMode = NSLineBreakByWordWrapping;
    return cell;
}

2️⃣计算 detailTextLabel 中的字符串大小,并返回高度。

You will also need to return a suitable height for the multi-line cell. A height of (44.0 + (numberOfLines - 1) * 19.0) should work fine.

文章作者称,还需要为多行 cell 返回一个合适的高度,高度的计算方式 =
(44.0 + (detailTextLabel 的行数 - 1)* 19.0)

而网上大多数获取高度的方法是,通过计算(包含给定字符串的)矩形大小来获得高度和宽度:

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

    // 参数一:最大宽高 44+19*4
    CGSize maxSize = CGSizeMake(355, MAXFLOAT);
    // 参数二:Font:
    UIFont *defaultFont = [UIFont systemFontOfSize:17.0f];
    // 参数三:String:
    NSArray *allKeys = [self.dataSourceDictionary allKeys];
    NSString *oneKey = allKeys[indexPath.row];
    NSString *string = self.dataSourceDictionary[oneKey];

    CGSize resultSize = [self sizeOfText:string font:defaultFont maxSize:maxSize];
    NSLog(@"result Size:(%f,%f) \n",resultSize.width,resultSize.height);

//    return resultSize.height + 23; // 23即消息上下的空间,可自由调整

    // 总高度 / 1行文本的高度 = 行数
    CGFloat labelNum = resultSize.height / 20.5;
    CGFloat height = 44.0 + (labelNum - 1) * 19.0;
    return height;

}

// 封装计算多行文本数据size方法
- (CGSize)sizeOfText:(NSString *)text font:(UIFont *)font maxSize:(CGSize)maxSize {
    NSDictionary *attrs = @{NSFontAttributeName:font};
    CGSize size = [text boundingRectWithSize:maxSize
                                     options:NSStringDrawingUsesLineFragmentOrigin
                                  attributes:attrs
                                     context:nil].size;
    return size;
}

这里会用到 UIKit 框架中 NSString 类的一个方法以计算包含字符串的矩形区域大小:

/**
 计算并返回(在当前图形上下文中)(使用给定选项和显示特征)的指定矩形内绘制的接收器的边界。

 @param size 要绘制的矩形的大小。
 @param options 字符串绘图选项。
 @param attributes 要应用于字符串的文本属性字典。 这些属性可以应用于NSAttributedString对象,但在NSString对象的情况下,这些属性适用于整个字符串,而不是字符串中的范围。
 @param context 用于接收器的字符串绘制上下文,指定最小比例因子和跟踪调整。
 @return 使用给定选项和显示特征绘制的接收器的边界。 从该方法返回的直线起点是第一个字形起点。
 */
- (CGRect)boundingRectWithSize:(CGSize)size
                       options:(NSStringDrawingOptions)options
                    attributes:(nullable NSDictionary<NSString *, id> *)attributes   
                       context:(nullable NSStringDrawingContext *)context NS_AVAILABLE(10_11, 7_0);

参数二是一个枚举类型:
类型为 NSStringDrawingOptions,用来指明绘制字符串时的渲染选项。各个选项如下:

typedef NS_OPTIONS(NSInteger, NSStringDrawingOptions) {
  // The specified origin is the line fragment origin, not the base line origin
  // 整个文本将以每行组成的矩形为单位计算整个文本的尺寸
  NSStringDrawingUsesLineFragmentOrigin = 1 << 0, 

  // Uses the font leading for calculating line heights
  // 使用字体的行间距来计算文本占用的范围,即每一行的底部到下一行的底部的距离计算
  NSStringDrawingUsesFontLeading = 1 << 1, 

  // Uses image glyph bounds instead of typographic bounds
  // 将文字以图像符号计算文本占用范围,而不是排版的边界
  NSStringDrawingUsesDeviceMetrics = 1 << 3,

  // Truncates and adds the ellipsis character to the last visible line if the text doesn't fit into the bounds specified.
  // Ignored if NSStringDrawingUsesLineFragmentOrigin is not also set.
  // 如果文本内容超出指定的矩形限制,文本将被截去并在最后一个字符后加上省略号。
  // 如果 NSStringDrawingUsesLineFragmentOrigin 没有设置,则该选项不生效
  NSStringDrawingTruncatesLastVisibleLine NS_ENUM_AVAILABLE(10_5, 6_0) = 1 << 5, 
} NS_ENUM_AVAILABLE(10_0, 6_0);

测试结果并不是很好,你会发现返回多行 Label 行高的时候会少计算了一行,猜测原因就在于 boundingRectWithSize:options:attributes: context: 传进去的第一个参数,该参数代表指定矩形区域的大小,但是 detailLabel 的宽度不是固定的,而是根据 textLabel 的宽度动态变化的。因此要继续往下做的话就要先计算出 textLabel 的宽度,再用 屏幕宽度 - textLabel 宽度 - 左右边距 -textLabeldetailLabel 之间的距离 算出 detailLabel 的实际宽度传进去。。。😂😂😂,越想越复杂,因此该方法目前来看还是更加适用于固定多行的时候吧。

参考

  • 优化 UITableViewCell 高度计算的那些事 @ForkingDog
  • UITableView 自动计算 cell 高度并缓存,再也不用管高度啦
  • 理解 NSStringDrawingOptions 每个选项的用法与意义
  • iOS--UILabel 字体默认宽度和高度
  • UI 高级控件入门 2——UITableView
  • UILabel 中一个很多人都会踩的坑

欢迎关注我的其它发布渠道