ios计算文本高度的几种方法

本来想尝试写个通过字符串长度得到高度的算法,结果发现不可行,这里记录一下整个过程。

起因

UITableviewCell的高度计算往往依赖于其内部UILabel或者UITextView的高度。

触发高度计算的场景有以下几种:

1 页面第一次加载

2 用户上拉或者下拉操作

3 代码主动触发

4 烂代码,不做缓存

许多开发同学喜欢用sizetoFit自适应,殊不知sizetoFit是一个很耗时的操作,不恰当的使用可能造成页面卡顿,滑动不流畅等等。所以一直想找一个替代方法。思路是通过字符串长度反算高宽,一般在TableViewCell里的文本宽度都是固定的(设计给的),所以最终是要计算文本框的高度。

思路:

如果我们知道一行字符的个数,就可以通过长度/单行个数反算高度。一行字数可以本地通过预处理等到,然后打包进安装包。理论上时间复杂度是O(1)。

问题:

上述算法基于以下前提:

每个字符占的宽度一致

但这是不可能的,肉眼都能发现数字和中文的字符宽度是不一样的。何况还有表情、火星文等奇奇怪怪的文本。

那么退而求其次,能否通过归类来局部加速呢? 比如字符“abc123"和字符"a1b2c3"都由三个字符和三个字母构成的,

其高度应该一致,那么我们可以统计高频的文本长度,来加速大部分的用户场景。

当然,这里也有前提:

某分类下的字符,其每个字符的宽度是一样的。

比如数字和数字的宽度一致,中文字符和中文字符宽度一致,表情的宽度一致等等。

为了验证这个结论,专门写了demo来分析。

计算文本的常用方法如下:

1 用UILabel来计算

NSMutableParagraphStyle*paragraphStyle = [[NSParagraphStyledefaultParagraphStyle]mutableCopy];
paragraphStyle.lineBreakMode=NSLineBreakByWordWrapping;
paragraphStyle.alignment=NSTextAlignmentLeft;
paragraphStyle.lineSpacing=2;

NSMutableAttributedString*attibuteStr = [[NSMutableAttributedStringalloc]initWithString:calcedStr];
[attibuteStraddAttribute:NSFontAttributeNamevalue:[UIFontsystemFontOfSize:14]range:NSMakeRange(0, calcedStr.length)];

[attibuteStraddAttribute:NSParagraphStyleAttributeNamevalue:paragraphStylerange:NSMakeRange(0, calcedStr.length)];

UILabel*label = [UILabelnew];
[labelsetAttributedText:attibuteStr];
label.numberOfLines=0;
label.textColor= [UIColorblackColor];
CGSizeresultSize = [labelsizeThatFits:limitSize];

这里有个新发现,以前一直以为UIView只能在主线程create和add,测试时发现可以在后台创建,并且能计算高度,以后可以预加载数据然后后台计算高度缓存,完全不用卡主线程

2 用[NSString boundingRectWithSize]

 NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
    paragraphStyle.alignment = NSTextAlignmentLeft;
    paragraphStyle.lineSpacing = 2;

NSDictionary* attributes = @{NSFontAttributeName:[UIFont systemFontOfSize:14],
                                 NSParagraphStyleAttributeName: paragraphStyle,
                                 NSForegroundColorAttributeName: [UIColor blackColor]};
    
CGRect rect = [calcedStr boundingRectWithSize:limitSize
                                     options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
                                  attributes:attributes
                                     context:nil];

3 用CoreText计算

   NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
    paragraphStyle.alignment = NSTextAlignmentLeft;
    paragraphStyle.lineSpacing = 2;
    NSMutableAttributedString *attibuteStr = [[NSMutableAttributedString alloc] initWithString:calcedString];
     [attibuteStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:14] range:NSMakeRange(0, calcedString.length)];
    [attibuteStr addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, calcedString.length)];
        CFAttributedStringRef attributedStringRef = (__bridge CFAttributedStringRef)attibuteStr;
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attributedStringRef);
    CFRange range = CFRangeMake(0, calcedString.length);
    CFRange fitCFRange = CFRangeMake(0, 0);
    CGSize newSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, range, NULL, limitSize, &fitCFRange);
        if (nil != framesetter) {
        CFRelease(framesetter);
        framesetter = nil;
    }
    return CGSizeMake(ceilf(newSize.width), ceilf(newSize.height));

结果:


IMG_2523.PNG

从真机实测的的结果:
时间: CoreText < NSString < UILabel
不过差异不大,毫秒级别,考虑到cpu时钟之类的干扰因素,可以认为是一致的。

差异最大的是计算结果CGSize,在代码中为了保持输入一致,设置相同的文本风格,包括字体大小,行距,对齐方式等:

屏幕快照 2017-09-05 09.23.18.png

但最后计算的CGSize不完全相同,差异如下:

Size.width:
CoreText > UILabel > NSString

Size.height:
UILabel > NSString > CoreText

CoreText 得到的结果更接近纯文本本身的高度,在网上搜了下CoreText的渲染原理,找到这张图

image.png

所以我猜CoreText比其他两种方式计算得到的Size更宽的原因是因为其他两种有默认的padding或者offset之类的东西,所以他们计算的时候要在原来的传入的Size上减去padding,因为文本展示的区间更小了,所以计算出来的CGSize要“瘦长”一点。查了一下UILable有没有padding之类的接口,没找到。UITextview倒是有,与我们的场景不相符,不纠结了。

从测试见过也可以得到一个结论:
NSString 和 UIlable 的计算结果也有差异,但是如果两个维度向上取整,那么就一致了,所以我们在实际写代码的时候一定要记得向上取整

另外还发现一个有用的方法,以前一直以为UIVidw不能在主线程之外的地方创建和调用,但是在实际调试中,发现可以异步后台线程创建UILabel,并且可以调用boundingRect方法得到计算结果。如果我们想做cell的高度预计算,可以用一个离屏的cell来实现,最简单

言归正传,我们初心还是想从字符串长度推测高度,不过从实际的测试结果来看,无法实现。

屏幕快照 2017-09-05 09.43.31.png

上图是纯数字的测试结果,最右边表示触发换行最小字符长度,可以看到单个数字本身的宽度也是不同的,数字“4”最宽,数字“1”最窄。

再看看26个字母:

屏幕快照 2017-09-05 09.45.49.png

字母本身的宽度也是不同的,字母“m”最宽,字母"i"和“j"最窄。

由于每个字符的宽度都不同,那么同样长度的字符会有不同的组合,所以想从长度推算宽度的方法失败。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容