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

2017-09-05  本文已影响0人  小孬_bc39

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

起因

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"最窄。

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

上一篇下一篇

猜你喜欢

热点阅读