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

posted @ 2020-10-09 12:17  阿甘左  阅读(413)  评论(0编辑  收藏  举报