iOS CoreText 高性能鏈?zhǔn)秸{(diào)用框架

前言

image

如果遇到上面一個(gè)需求, 你會(huì)怎么處理, 若干個(gè) UILabel + UIImageView? NSAttributedString拼接? CoreText?

我相信不論是哪種方式代碼量都不小, 并且難以復(fù)用, 其他語(yǔ)言寫(xiě)富文本是那么輕松, Android 天生支持簡(jiǎn)單 HTML, RN(JS) 標(biāo)簽套標(biāo)簽, 而只要用過(guò) iOS 中的富文本都會(huì)覺(jué)得難用... 目前業(yè)界功能強(qiáng)大、較為好用的是 YYText, 但設(shè)計(jì)思想是盡可能與 UILabel挎塌、UITextView 相似, 所以相對(duì)使用也不是特別簡(jiǎn)單, 而且框架較重. 基于現(xiàn)狀開(kāi)發(fā)了一套輕量的框架, 滿足大部分富文本需求, 并且提供了手勢(shì)響應(yīng)徘六、 繪制回調(diào)、 圖文對(duì)齊榴都、 CoreText 屬性擴(kuò)展待锈、 支持網(wǎng)絡(luò)圖片、 異步繪制性能優(yōu)化, 最重要的是使用簡(jiǎn)單, 通過(guò)鏈?zhǔn)秸Z(yǔ)法輕松寫(xiě)出一篇圖文混排文本.

示例說(shuō)明

如圖所示一篇圖文混排, 涉及到字體, 顏色, 字間距, 行間距, 圖片對(duì)齊, 文字對(duì)齊, 描邊等等屬性, 還有網(wǎng)絡(luò)圖片與本地圖片混排, 手勢(shì)響應(yīng)等需求, 使用本框架可以下面這樣實(shí)現(xiàn):

    //...省略常量聲明

    //標(biāo)題
    title.font(titleFont).color(titleColor).onClicked(titleOnClicked).onLayout(titleOnLayout);
    //首段
    firstPara.color(firstParaColor).align(@0);
    //圖片需要用一個(gè)空字符串起頭
    NSString *webImageString = @"".append(webImageURL).font(separateLineFont).minLineHeight(@100);
    //分割線
    separateLine.font(separateLineFont).strokeColor(separateLineColor).strokeWidth(@1);
    //本地圖片
    NSString *locolImageString = @"".append(locolImage);
    //最后一段
    lastPara.font(lastParaFont).align(@1);
    //書(shū)名
    bookName.font(bookNameFont).color(bookNameColor).onClicked(bookOnClicked).align(@1);
    //引用
    quote.color(quoteColor).letterSpace(@0).minLineSpace(@8).align(@0);
    
    //設(shè)置全局默認(rèn)屬性, 優(yōu)先級(jí)低于指定屬性
    NSString *defaultAttributes = @"".entire()
    .maxSize(maxSize).align(@2).letterSpace(@3).minLineHeight(@20).maxLineHeight(@20).imageAlign(@1).onClicked(textOnClicked).imageSize(imageSize);
    
    //拼接
    title.append(firstPara).append(webImageString).append(separateLine).append(locolImageString).append(lastPara).append(bookName).append(quote)
    //設(shè)置默認(rèn)屬性
    .append(defaultAttributes)
    //繪制View
    .drawView(^(UIView *drawView) {
        [self.view addSubview:drawView];
    });

甚至可以這樣實(shí)現(xiàn):

    //...省略常量聲明

    @""
    //拼接全文
    .append(title).font(titleFont).color(titleColor).onClicked(titleOnClicked).onLayout(titleOnLayout)
    .append(firstPara).color(firstParaColor).align(@0)
    .append(webImageURL).font(separateLineFont).minLineHeight(@100)
    .append(separateLine).font(separateLineFont).strokeColor(separateLineColor).strokeWidth(@1)
    .append(locolImage)
    .append(lastPara).font(lastParaFont).align(@1)
    .append(bookName).font(bookNameFont).color(bookNameColor).onClicked(bookOnClicked).align(@1)
    .append(quote).color(quoteColor).letterSpace(@0).minLineSpace(@8).align(@0)
    //設(shè)置默認(rèn)屬性
    .entire().maxSize(maxSize).align(@2).letterSpace(@3).minLineHeight(@20).maxLineHeight(@20).imageAlign(@1).onClicked(textOnClicked).imageSize(imageSize)
    //繪制
    .drawView(^(UIView *drawView) {
        [self.view addSubview:drawView];
    });

核心方法與屬性

對(duì) NSString 的擴(kuò)展

核心方法
  • append(id content)

      拼接
      content 可以是文本(NSString)嘴高、圖片(UIImage)竿音、圖片鏈接(NSURL)(必須指定imageSize屬性)、視圖(CALayer/UIView)
    
  • entire()

      設(shè)置整段富文本
      優(yōu)先級(jí)低于指定屬性, 較為重要的屬性 maxSize 設(shè)置繪制約束, 部分段落屬性只在整段中設(shè)置生效
    
  • drawLayer(^(CALayer *drawLayer)completion)

      繪制layer, 無(wú)法響應(yīng)手勢(shì)
    
  • drawView(^(UIView *drawView)completion)

      繪制View, 可響應(yīng)手勢(shì)
    
屬性
通用屬性
  • verticalOffset 垂直偏移
  • onClicked 點(diǎn)擊回調(diào)
  • onLayout 展示回調(diào)
  • cacheFrame 緩存該段文本繪制位置
字符串屬性
  • font 字體: 文字字體/圖片居中對(duì)齊字體
  • color 顏色
  • letterSpace 字間距
  • strokeWidth 描邊寬度, 整數(shù)為鏤空, Color不生效; 負(fù)數(shù)Color生效
  • strokeColor 描邊顏色
  • verticalForm 文字繪制隨文字書(shū)寫(xiě)方向, 默認(rèn) 否(0), 是(非0)
  • underline 下劃線類型, 整形, 0為none, 1為細(xì)線 2為加粗 9為雙條 參考 CTUnderlineStyle(僅枚舉了三種, 其他值也有不同效果)
圖片屬性
  • imageSize 圖片尺寸, 默認(rèn)為圖片本身尺寸, 會(huì)根據(jù)圖片縮放(2x 3x)自動(dòng)調(diào)整
  • imageAlign 圖片對(duì)齊模式, 0為默認(rèn), 基準(zhǔn)線對(duì)齊. 1為居中對(duì)齊至特定字體大小 參看 ZJTextImageAlign
段落屬性
  • maxSize 繪制的約束尺寸, 默認(rèn)不限制
  • minLineSpace 最小行間距
  • maxLineSpace 最大行間距
  • minLineHeight 最小行高
  • maxLineHeight 最小行高
  • align 對(duì)齊, 整形, 0為默認(rèn)靠左 1為靠右 2為居中, 參考 CTTextAlignment
  • lineBreakMode 對(duì)齊, 整形, 參考 NSLineBreakMode

性能

總體采用 CoreText + 異步繪制圖片完成, 理論上性能會(huì)比較高, 經(jīng)過(guò)測(cè)試如下數(shù)據(jù)供參考:

內(nèi)容: 一段文本加上兩張圖片

機(jī)型: iPhone 6

測(cè)試結(jié)果:

常規(guī)(使用NSAttributedString + UILabel)過(guò)程: 創(chuàng)建->顯示(繪制)
常規(guī)分析:

  1. 主線程代碼在 28ms 左右. (主線程代碼開(kāi)始 至 結(jié)束耗時(shí))
  2. UILabel 顯示(繪制)耗時(shí)在 42ms 左右. (addSubview 至 drawRect 耗時(shí))
  3. 綜合耗時(shí) 70ms 左右, 全部在主線程

異步繪制(本框架)過(guò)程: 創(chuàng)建->異步繪制->顯示
異步繪制分析:

  1. 主線程(創(chuàng)建)代碼在 28ms 左右. (主線程代碼開(kāi)始 至 結(jié)束耗時(shí))
  2. 創(chuàng)建(主線程) + 異步繪制耗時(shí) 84ms 左右. (主線程代碼開(kāi)始 至 繪制出圖片回調(diào))
  3. 由 1拴驮、2 得出子線程繪制耗時(shí) 56ms 左右, 另外經(jīng)過(guò)多次試驗(yàn)(大段文字繪制)得出繪制復(fù)雜的段落也耗時(shí)增長(zhǎng)較少
  4. 顯示耗時(shí) 0.75 ms 左右. (addSubview 至 drawRect 耗時(shí))
  5. 綜合耗時(shí) 85ms 左右, 其中主線程 29ms, 子線程 56ms

結(jié)論:

  1. 相較于常規(guī)方式降低了主線程壓力 70ms -> 29ms
  2. 越復(fù)雜的文本收益越高(多控件合一, 異步繪制), 上圖中大段富文本繪制時(shí)間也只多了 15ms, 耗時(shí)增長(zhǎng)少
  3. 總體耗時(shí)增加了15ms, 都在子線程, 畢竟處理的邏輯比系統(tǒng)的多.

安裝

Github

ZJAttributedText

Pod

pod 'ZJAttributedText'

本框架依賴 SDWebImage (幾乎所有App都集成了, 可以共用一套緩存邏輯)

尾巴

內(nèi)部實(shí)現(xiàn)代碼不多, 幾乎所有步驟都添加了注釋, 如果需要學(xué)習(xí) CoreText, 異步繪制, 鏈?zhǔn)秸Z(yǔ)法, 還算是個(gè)不錯(cuò)的 Demo, 如果大家感興趣, 可以補(bǔ)充下 CoreText 相關(guān)內(nèi)容, 這部分網(wǎng)上的資料都比較老, 錯(cuò)誤也比較多. 歡迎 issue 與 star~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末春瞬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子套啤,更是在濱河造成了極大的恐慌宽气,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件潜沦,死亡現(xiàn)場(chǎng)離奇詭異抹竹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)止潮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門窃判,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人喇闸,你說(shuō)我怎么就攤上這事袄琳。” “怎么了燃乍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵唆樊,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我刻蟹,道長(zhǎng)逗旁,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任舆瘪,我火速辦了婚禮片效,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘英古。我一直安慰自己淀衣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布召调。 她就那樣靜靜地躺著膨桥,像睡著了一般蛮浑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上只嚣,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天沮稚,我揣著相機(jī)與錄音,去河邊找鬼册舞。 笑死蕴掏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的环础。 我是一名探鬼主播囚似,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了于毙?” 一聲冷哼從身側(cè)響起纷跛,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宣虾,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡角雷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了性穿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勺三。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖需曾,靈堂內(nèi)的尸體忽然破棺而出吗坚,到底是詐尸還是另有隱情,我是刑警寧澤呆万,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布商源,位于F島的核電站,受9級(jí)特大地震影響谋减,放射性物質(zhì)發(fā)生泄漏牡彻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一出爹、第九天 我趴在偏房一處隱蔽的房頂上張望庄吼。 院中可真熱鬧,春花似錦严就、人聲如沸霸褒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)废菱。三九已至技矮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間殊轴,已是汗流浹背衰倦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留旁理,地道東北人樊零。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像孽文,于是被迫代替她去往敵國(guó)和親驻襟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354