iOS圖文混排
圖文混排, 文字點選事件, 文字動畫(不在討論之內,可以通過對view來處理),如果是簡單的操作,可以用UITextView
來處理,當需要特殊的定製化的時候可以採用CoreText
,它是一個強大的文字處理框架,支援所有文字排列相關的定製化操作,配合CoreGraphics
可以實現圖文混排功能。
實現原理
CoreText是一個底層的文字繪製框架,它真正繪製的其實只是文字部分,如果涉及到圖片還是需要手動去的繪製的,它主要是通過對文字的段落樣式,行樣式,字型大小,樣式配置資訊統一搜集後,計算出它的layout再進行繪製,它主要包括以下幾個關鍵類。
- CTFramesetter (排版)
- CFFrame (段落,框架)
- CFLine (行)
- CTRun (文字)
- CFTTypeSetter(排字)
原點位置
- CoreText中的文字原點位置和UIView是基於X軸承映象對稱,Y座標相反
Appkit->NSView: leftBottom
UIKit->UIView: topRight
CoreGraphic->Context: letBottom
CoreText物件模型
CFAttributedStringRef: 屬性字串,用於儲存所徐喲啊繪製額文字字元和屬性
CTFramesetterRef: 對應的是CTFramesetter,通過
CFAttributedStringRef
CTFrame
的生產工廠,負責根據對應的Path
生成對應的CTFrame
CTFrame: 可以通過
CTFrameDraw
函式直接繪製到Context
上,在繪製之前可以通過CTLine
進行一些引數的微調CTLine:
CTFrame
內部是由多個CTLine
組成,每個CTLine
可以看作一行CTRun: 或者叫做
Glyph Run
,每個CTLine
是由多惡搞CTRun
組成,CTRun
是一組顯示風格一致的文字(類似Flutter中的TextSpan
),是一組有著相同attributes
(屬性)的字型集合體
通常處理步驟
- 獲取當前的上下文物件
- 翻轉座標系,Y軸對稱
- 建立
AttributedString
CTFramesetterRef
- 建立繪製區域:
CGPathRef
- 根據
CTFramesetterRef
和CGPathRef
建立CTFrame
CGFrameDraw
繪製文字
func layoutParagraph() {
let context = UIGraphicsGetCurrentContext()!
//1. 反轉Y,Coregraphics的座標系在坐下角
context.translateBy(x: 0, y: self.bounds.size.height);
context.scaleBy(x: 1.0, y: -1.0)
context.textMatrix = CGAffineTransform.identity;
//2. 建立路徑
let path = CGMutablePath();
let rect = CGRect(x: 10.0, y: 10.0, width: 200.0, height: 200.0);
path.addRect(rect)
//3.建立`CFAttributedString`
let data = "Hello, World! I know nothing in the world that has as much power as a word. Sometimes I write one, and I look at it, until it begins to shine.".withCString{ $0 }
let cfString = CFStringCreateWithCString(kCFAllocatorDefault,data, CFStringBuiltInEncodings.UTF8.rawValue)
let attrString =
CFAttributedStringCreateMutable(kCFAllocatorDefault, 0)!;
CFAttributedStringReplaceString(attrString, CFRangeMake(0, 0), cfString)
//4.設定部分文字的屬性
let colorSpace = CGColorSpaceCreateDeviceRGB()
let components = [CGFloat(0.0), CGFloat(0.3), CGFloat(0.3), CGFloat(0.8) ].withUnsafeBufferPointer{ $0 }
let redColor = CGColor(colorSpace: colorSpace, components: components.baseAddress!)
CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 12),
kCTForegroundColorAttributeName, redColor);
//5.建立文字佈局物件
let framesetter = CTFramesetterCreateWithAttributedString(attrString)
//6. 繪製片段
let frame = CTFramesetterCreateFrame(framesetter,
CFRangeMake(0,0), path, nil);
//7.執行繪製
CTFrameDraw(frame, context)
}
區域性點選判斷
- 由於
CoreText
中的文字是基於CTFrame
進行排版的,它包括了多個CTLine
,所以很容易得到各個line
的位置和大小,判斷點選在不在某個line
上,CTLine
又可以進一步判斷點選的文字是否在CTLine
的指定座標上,通過便利這個String的NSTextCheckingResult
結果,根據Rang
計算出文字的具體位置。
圖文混排
CoreText
本身不支援圖片繪製,圖片繪製需要使用Core Graphics
,CoreText
只是通過CCTRun
的設定為圖片的繪製提供預留的空間,這個設定需要使用CTRunDelegate
,CTRunDelegate作為CTRun相關屬性或操作擴充套件的一個入口,使得我們可以對CTRun做一些自定義的行為。為圖片留位置的方法就是加入一個空白的CTRun,自定義其ascent,descent,width等引數,使得繪製文字的時候留下空白位置給相應的圖片。然後圖片在相應的空白位置上使用Core Graphics介面進行繪製。使用CTRunDelegateCreate可以建立一個CTRunDelegate,它接收兩個引數,一個是callbacks結構體,一個是所有callback呼叫的時候需要傳入的物件。 callbacks的結構體為CTRunDelegateCallbacks,主要是包含一些回撥函式,比如有返回當前run的ascent,descent,width這些值的回撥函式,至於函式中如何鑑別當前是哪個run,可以在CTRunDelegateCreate的第二個引數來達到目的,因為CTRunDelegateCreate的第二個引數會作為每一個回撥呼叫時的入參。
參考連結
CoreText Programming Guide: https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/CoreText_Programming/Introduction/Introduction.html#//apple_ref/doc/uid/TP40005533