iOS 编程:UITableViewCell高度自适应

第三方框架

✨方法一:使用 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的实际宽度传进去。。。😂😂😂,越想越复杂,因此该方法目前来看还是更加适用于固定多行的时候吧。

参考

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

推荐阅读更多精彩内容