本來想嘗試寫個通過字符串長度得到高度的算法退敦,結(jié)果發(fā)現(xiàn)不可行谅年,這里記錄一下整個過程。
起因
UITableviewCell的高度計算往往依賴于其內(nèi)部UILabel或者UITextView的高度亥鬓。
觸發(fā)高度計算的場景有以下幾種:
1 頁面第一次加載
2 用戶上拉或者下拉操作
3 代碼主動觸發(fā)
4 爛代碼,不做緩存
許多開發(fā)同學(xué)喜歡用sizetoFit自適應(yīng)丸边,殊不知sizetoFit是一個很耗時的操作,不恰當(dāng)?shù)氖褂每赡茉斐身撁婵D荚孵,滑動不流暢等等妹窖。所以一直想找一個替代方法。思路是通過字符串長度反算高寬收叶,一般在TableViewCell里的文本寬度都是固定的(設(shè)計給的)骄呼,所以最終是要計算文本框的高度。
思路:
如果我們知道一行字符的個數(shù),就可以通過長度/單行個數(shù)反算高度蜓萄。一行字?jǐn)?shù)可以本地通過預(yù)處理等到隅茎,然后打包進安裝包。理論上時間復(fù)雜度是O(1)嫉沽。
問題:
上述算法基于以下前提:
每個字符占的寬度一致
但這是不可能的辟犀,肉眼都能發(fā)現(xiàn)數(shù)字和中文的字符寬度是不一樣的。何況還有表情绸硕、火星文等奇奇怪怪的文本堂竟。
那么退而求其次,能否通過歸類來局部加速呢玻佩? 比如字符“abc123"和字符"a1b2c3"都由三個字符和三個字母構(gòu)成的出嘹,
其高度應(yīng)該一致,那么我們可以統(tǒng)計高頻的文本長度咬崔,來加速大部分的用戶場景税稼。
當(dāng)然,這里也有前提:
某分類下的字符垮斯,其每個字符的寬度是一樣的郎仆。
比如數(shù)字和數(shù)字的寬度一致,中文字符和中文字符寬度一致兜蠕,表情的寬度一致等等丸升。
為了驗證這個結(jié)論,專門寫了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];
這里有個新發(fā)現(xiàn),以前一直以為UIView只能在主線程create和add墩剖,測試時發(fā)現(xiàn)可以在后臺創(chuàng)建猴凹,并且能計算高度,以后可以預(yù)加載數(shù)據(jù)然后后臺計算高度緩存岭皂,完全不用卡主線程
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));
結(jié)果:
從真機實測的的結(jié)果:
時間: CoreText < NSString < UILabel
不過差異不大郊霎,毫秒級別,考慮到cpu時鐘之類的干擾因素爷绘,可以認(rèn)為是一致的书劝。
差異最大的是計算結(jié)果CGSize,在代碼中為了保持輸入一致土至,設(shè)置相同的文本風(fēng)格购对,包括字體大小,行距陶因,對齊方式等:
但最后計算的CGSize不完全相同骡苞,差異如下:
Size.width:
CoreText > UILabel > NSString
Size.height:
UILabel > NSString > CoreText
CoreText 得到的結(jié)果更接近純文本本身的高度,在網(wǎng)上搜了下CoreText的渲染原理,找到這張圖
所以我猜CoreText比其他兩種方式計算得到的Size更寬的原因是因為其他兩種有默認(rèn)的padding或者offset之類的東西解幽,所以他們計算的時候要在原來的傳入的Size上減去padding贴见,因為文本展示的區(qū)間更小了,所以計算出來的CGSize要“瘦長”一點躲株。查了一下UILable有沒有padding之類的接口片部,沒找到。UITextview倒是有霜定,與我們的場景不相符档悠,不糾結(jié)了。
從測試見過也可以得到一個結(jié)論:
NSString 和 UIlable 的計算結(jié)果也有差異然爆,但是如果兩個維度向上取整站粟,那么就一致了,所以我們在實際寫代碼的時候一定要記得向上取整
另外還發(fā)現(xiàn)一個有用的方法曾雕,以前一直以為UIVidw不能在主線程之外的地方創(chuàng)建和調(diào)用奴烙,但是在實際調(diào)試中,發(fā)現(xiàn)可以異步后臺線程創(chuàng)建UILabel剖张,并且可以調(diào)用boundingRect方法得到計算結(jié)果切诀。如果我們想做cell的高度預(yù)計算,可以用一個離屏的cell來實現(xiàn)搔弄,最簡單
言歸正傳幅虑,我們初心還是想從字符串長度推測高度,不過從實際的測試結(jié)果來看顾犹,無法實現(xiàn)倒庵。
上圖是純數(shù)字的測試結(jié)果,最右邊表示觸發(fā)換行最小字符長度炫刷,可以看到單個數(shù)字本身的寬度也是不同的擎宝,數(shù)字“4”最寬,數(shù)字“1”最窄浑玛。
再看看26個字母:
字母本身的寬度也是不同的绍申,字母“m”最寬,字母"i"和“j"最窄顾彰。
由于每個字符的寬度都不同极阅,那么同樣長度的字符會有不同的組合,所以想從長度推算寬度的方法失敗涨享。