1. 程式人生 > 實用技巧 >iOS圖文混排

iOS圖文混排

圖文混排, 文字點選事件, 文字動畫(不在討論之內,可以通過對view來處理),如果是簡單的操作,可以用UITextView來處理,當需要特殊的定製化的時候可以採用CoreText,它是一個強大的文字處理框架,支援所有文字排列相關的定製化操作,配合CoreGraphics可以實現圖文混排功能。

實現原理

CoreText是一個底層的文字繪製框架,它真正繪製的其實只是文字部分,如果涉及到圖片還是需要手動去的繪製的,它主要是通過對文字的段落樣式,行樣式,字型大小,樣式配置資訊統一搜集後,計算出它的layout再進行繪製,它主要包括以下幾個關鍵類。

  1. CTFramesetter (排版)
  2. CFFrame (段落,框架)
  3. CFLine (行)
  4. CTRun (文字)
  5. 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
  • 根據CTFramesetterRefCGPathRef建立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

YYText: https://github.com/ibireme/YYText