輕量富文本異步繪製框架

前言

若是遇到上面一個需求, 你會怎麼處理, 若干個 UILabel + UIImageView? NSAttributedString拼接? CoreText?git

我相信不管是哪一種方式代碼量都不小, 而且難以複用, 其餘語言寫富文本是那麼輕鬆, Android 天生支持簡單 HTML, RN(JS) 標籤套標籤, 而只要用過 iOS 中的富文本都會以爲難用... 目前業界功能強大、較爲好用的是 YYText, 但設計思想是儘量與 UILabel、UITextView 類似, 因此相對使用也不是特別簡單, 並且框架較重. 基於現狀開發了一套輕量的框架, 知足大部分富文本需求, 而且提供了手勢響應、 繪製回調、 圖文對齊、 CoreText 屬性擴展、 支持網絡圖片、 異步繪製性能優化, 最重要的是使用簡單, 經過鏈式語法輕鬆寫出一篇圖文混排文本.github

示例說明

如圖所示一片圖文混排, 涉及到字體, 顏色, 字間距, 行間距, 圖片對齊, 文字對齊, 描邊等等屬性, 還有網絡圖片與本地圖片混排, 手勢響應等需求, 使用本框架能夠下面這樣實現:web

//...省略常量聲明

TextBuild
.append(title).font(titleFont).color(titleColor).onClicked(titleOnClicked).onLayout(titleOnLayout)
.append(firstPara).color(firstParaColor).align(@0)
.append(webImage).font(separateLineFont).minLineHeight(@100)
.append(separateLine).font(separateLineFont).strokeColor(separateLineColor).strokeWidth(@1).horizontalOffset(@30)
.append(locolImage).horizontalOffset(@30)
.append(lastPara).font(lastParaFont).align(@1).maxLineHeight(@20)
.append(bookName).font(bookNameFont).color(bookNameColor).onClicked(bookOnClicked).align(@1)
.append(lineLayer).attachSize(lineLayerSize)
.append(quote).color(quoteColor).letterSpace(@0).minLineSpace(@8).align(@0)
.append(buyButton).attachSize(buyButtonSize).attachAlign(@0)
//設置全局默認屬性, 優先級低於指定屬性
.entire().maxSize(maxSize).align(@2).letterSpace(@3).minLineHeight(@20).attachAlign(@1).onClicked(textOnClicked).attachSize(attachSize).shadow(shadow).cornerRadius(@50).backgroundLayer(gradientLayer).horizontalMargin(@10).preferHeight(@(preferHeight))
//繪製View
.drawView(^(UIView *drawView) {
    [self.view addSubview:drawView];
});
複製代碼

而在實際需求中也能夠根據不一樣條件對 NSString 進行組合, 最後繪製:緩存

//...省略常量聲明

//拼接文章
//標題
NSString *titleString = TextBuild.append(title).font(titleFont).color(titleColor).onClicked(titleOnClicked).onLayout(titleOnLayout);
//首段
NSString *firstParaString = TextBuild.append(firstPara).color(firstParaColor).align(@0);
//圖片須要用一個空字符串起頭
NSString *webImageString = TextBuild.append(webImage).font(separateLineFont).minLineHeight(@100);
//分割線
NSString *separateLineString = TextBuild.append(separateLine).font(separateLineFont).strokeColor(separateLineColor).strokeWidth(@1).horizontalOffset(@30);
//本地圖片
NSString *locolImageString = TextBuild.append(locolImage).horizontalOffset(@30);
//最後一段
NSString *lastParaString = TextBuild.append(lastPara).font(lastParaFont).align(@1).maxLineHeight(@20);
//書名
NSString *bookNameString = TextBuild.append(bookName).font(bookNameFont).color(bookNameColor).onClicked(bookOnClicked).align(@1).maxLineHeight(@20);
//引用線Layer
NSString *lineLayerString = TextBuild.append(lineLayer).attachSize(lineLayerSize);
//引用
NSString *quoteString = TextBuild.append(quote).color(quoteColor).letterSpace(@0).minLineSpace(@8).align(@0);
//按鈕
NSString *buttonString = TextBuild.append(buyButton).attachSize(buyButtonSize).attachAlign(@0);

//設置全局默認屬性, 優先級低於指定屬性
NSString *defaultAttributes = TextBuild.entire()
.maxSize(maxSize).align(@2).letterSpace(@3).minLineHeight(@20).attachAlign(@1).onClicked(textOnClicked).attachSize(attachSize).shadow(shadow).cornerRadius(@50).backgroundLayer(gradientLayer).horizontalMargin(@10).preferHeight(@(preferHeight));

//拼接
TextBuild
.append(titleString)
.append(firstParaString)
.append(webImageString)
.append(separateLineString)
.append(locolImageString)
.append(lastParaString)
.append(bookNameString)
.append(lineLayerString)
.append(quoteString)
.append(buttonString)
//設置默認屬性
.append(defaultAttributes)
//繪製Layer
.drawLayer(^(CALayer *drawLayer) {
    [self.view.layer addSublayer:drawLayer];
});
複製代碼

核心方法與屬性

對 NSString 的擴展, 操做字符串生成繪製對應視圖ruby

核心方法

  • append(id content)性能優化

    拼接
      content 能夠是文本(NSString)、圖片(UIImage)、圖片連接(NSURL)(必須指定attachSize屬性)、視圖(CALayer/UIView)
    複製代碼
  • entire()網絡

    設置整段富文本
      優先級低於指定屬性, 較爲重要的屬性 maxSize 設置繪製約束, 部分段落屬性只在整段中設置生效
    複製代碼
  • drawLayer(^(CALayer *drawLayer)completion)app

    繪製layer, 沒法響應手勢, 當有UIView混排建議使用 drawView
    複製代碼
  • drawView(^(UIView *drawView)completion)框架

    繪製View, 可響應手勢, 建議使用此API
    複製代碼

屬性

通用屬性
  • verticalOffset 垂直偏移
  • horizontalOffset 水平方向偏移
  • onClicked 點擊回調
  • onLayout 展現回調
  • cacheFrame 緩存該段文本繪製位置
  • minLineSpace 最小行間距
  • maxLineSpace 最大行間距
  • minLineHeight 最小行高
  • maxLineHeight 最小行高
  • align 對齊, 整形, 0爲默認靠左 1爲靠右 2爲居中, 參考 CTTextAlignment
  • lineBreakMode 對齊, 整形, 參考 NSLineBreakMode
字符串屬性
  • font 字體: 文字字體/圖片居中對齊字體
  • color 顏色
  • letterSpace 字間距
  • strokeWidth 描邊寬度, 整數爲鏤空, Color不生效; 負數Color生效
  • strokeColor 描邊顏色
  • verticalForm 文字繪製隨文字書寫方向, 默認 否(0), 是(非0)
  • underline 下劃線類型, 整形, 0爲none, 1爲細線 2爲加粗 9爲雙條 參考 CTUnderlineStyle(僅枚舉了三種, 其餘值也有不一樣效果)
圖片屬性
  • attachSize 圖片尺寸, 默認爲圖片自己尺寸, 會根據圖片縮放(2x 3x)自動調整
  • attachAlign 圖片對齊模式, 0爲默認, 基準線對齊. 1爲居中對齊至特定字體大小 參看 ZJTextattachAlign
段落屬性
注: 下面屬性多段文本拼接只在 entire() 函數後生效; 若只有一段有效文本(非空字符及其餘類型), 也能夠直接生效.
  • maxSize 繪製的約束尺寸, 默認不限制
  • shadow 文字陰影, 對全文生效
  • preferHeight 指望繪製高度, 內容居中
  • verticalMargin 垂直方向間距, 若設置了 preferHeight 此屬性不生效
  • horizontalMargin 水平方向間距
  • backgroundColor 背景顏色
  • backgroundLayer 背景視圖, 經常使用圖片背景/漸變色背景
  • cornerRadius 圓角

性能

整體採用 CoreText + 異步繪製圖片完成, 理論上性能會比較高, 通過測試以下數據供參考:異步

內容: 一段文本加上兩張圖片

機型: iPhone 6

測試結果:

常規(使用NSAttributedString + UILabel)過程: 建立->顯示(繪製) 常規分析:

  1. 主線程代碼在 28ms 左右. (主線程代碼開始 至 結束耗時)
  2. UILabel 顯示(繪製)耗時在 42ms 左右. (addSubview 至 drawRect 耗時)
  3. 綜合耗時 70ms 左右, 所有在主線程

異步繪製(本框架)過程: 建立->異步繪製->顯示 異步繪製分析:

  1. 主線程(建立)代碼在 28ms 左右. (主線程代碼開始 至 結束耗時)
  2. 建立(主線程) + 異步繪製耗時 84ms 左右. (主線程代碼開始 至 繪製出圖片回調)
  3. 由 一、2 得出子線程繪製耗時 56ms 左右, 另外通過屢次試驗(大段文字繪製)得出繪製複雜的段落也耗時增加較少
  4. 顯示耗時 0.75 ms 左右. (addSubview 至 drawRect 耗時)
  5. 綜合耗時 85ms 左右, 其中主線程 29ms, 子線程 56ms

結論:

  1. 相較於常規方式下降了主線程壓力 70ms -> 29ms
  2. 越複雜的文本收益越高(多控件合一, 異步繪製), 上圖中大段富文本繪製時間也只多了 15ms, 耗時增加少
  3. 整體耗時增長了15ms, 都在子線程, 畢竟處理的邏輯比系統的多.
  4. 整體性能與 YYText 相仿

安裝

Github

ZJAttributedText

Pod

pod 'ZJAttributedText'
複製代碼

本框架依賴 SDWebImage (幾乎全部App都集成了, 能夠共用一套緩存邏輯)

尾巴

內部實現代碼很少, 幾乎全部步驟都添加了註釋, 若是須要學習 CoreText, 異步繪製, 鏈式語法, 還算是個不錯的 Demo, 若是你們感興趣, 能夠補充下 CoreText 相關內容, 這部分網上的資料都比較老, 錯誤也比較多. 歡迎 issue 與 star~

相關文章
相關標籤/搜索