工作生活

富文本 CoreText 和 TextKit

2020-02-11  本文已影响0人  小凡凡520
一、CoreText

CoreText是Mac OS和iOS系统中处理文本的底层API, 不管是使用OC还是swift, 实际我们使用CoreText都还是间接或直接使用C语言在写代码。CoreText是iOS和Mac OS中文本处理的根基, TextKit和WebKit都是构建于其上。

二、说明
1、CTFrame可以想象成画布, 画布的大小范围由CGPath决定
2、CTFrame由很多CTLine组成, CTLine表示为一行
3、CTLine由多个CTRun组成, CTRun相当于一行中的多个块, 但是CTRun不需要你自己创建, 由NSAttributedString的属性决定, 系统自动生成。每个CTRun对应不同属性。
4、CTFramesetter是一个工厂, 创建CTFrame, 一个界面上可以有多个CTFrame
5、CTFrame就是一个基本画布,然后一行一行绘制。 CoreText会自动根据传入的NSAttributedString属性创建CTRun,包括字体样式,颜色,间距等
1642760-d88061e35bc6ba1d.png
三、使用

CoreText是需要自己处理绘制,不像UILabel等最上层的控件 ,所以我们必须在drawRect中绘制,为了更好地使用,我们稍微封装一下,自定义一个UIView。

我们在使用上层的控件时,坐标系的原点在左上角,而底层的Core Graphics的坐标系原点则是在左下角,以下是一个最基本的绘制示例:

class Myview: UIView {
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        //step 1:获取当前画布的上下文
        if let context = UIGraphicsGetCurrentContext() {
            //step 2:
            let path = CGMutablePath.init()
            path.addRect(self.bounds)
            //step 3:
            let attributedString = NSMutableAttributedString(string: "单身公寓")
            //step 4:
            let framesetter = CTFramesetterCreateWithAttributedString(attributedString)
            let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attributedString.length), path, nil)
            //step 5:
            // CTFrameDraw
            CTFrameDraw(frame, context)

            // 按CTLine绘制
            // 1.获得CTLine数组
            let lines = CTFrameGetLines(frame)
            // 2.获得行数
            let numberOfLines = CFArrayGetCount(lines)
            // 3.获得每一行的origin, CoreText的origin是在字形的baseLine处的, 请参考字形图
            var lineOrigins = [CGPoint](repeating: CGPoint.zero, count: numberOfLines)
            CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), &lineOrigins)
            // 4.遍历每一行进行绘制
            for index in 0..<numberOfLines {
                let origin = lineOrigins[index]
                // 参考: http://swifter.tips/unsafe/
                let line = unsafeBitCast(CFArrayGetValueAtIndex(lines, index), to: CTLine.self)
                // 设置每一行的位置
                context.textPosition = origin
                // 开始一行的绘制
                CTLineDraw(line, context)
            }

            // 1.获得CTLine数组
            let lines = CTFrameGetLines(frame)
            // 2.获得行数
            let numberOfLines = CFArrayGetCount(lines)
            // 4.遍历每一行进行绘制
            for index in 0..<numberOfLines {
                // 参考: http://swifter.tips/unsafe/
                let line = unsafeBitCast(CFArrayGetValueAtIndex(lines, index), to: CTLine.self)
                drawLine(line: line, context: context)
            }
        }
    }

    // 按CTRun绘制
    func drawLine(line: CTLine, context: CGContext) {
        let runs = CTLineGetGlyphRuns(line) as Array
        runs.forEach { run in
            CTRunDraw(run as! CTRun, context, CFRangeMake(0, 0))
        }
    }
}
1642760-a3b04adcefbddc3c.png

结果分析:发现文案是反的。原因就是因为coreText的坐标系是和UIKit的坐标系不一样的:

1642760-0d735191bf8d9143.jpeg

如上图,CoreText是基于CoreGraphics的,所以坐标系原点是左下角,我们需要进行翻转。将Y轴从向上转换为向下。

context.textMatrix = CGAffineTransform.identity
context.translateBy(x: 0, y: self.bounds.size.height)
context.scaleBy(x: 1.0, y: -1.0)
1642760-688ae81a11ed68b8.png
四、图文混排

CoreText本身是不提供UIImage的绘制,所以UIImage肯定只能通过Core Graphics绘制,但是绘制时双必须要知道此绘制单元的长宽,庆幸的是CoreText绘制的最小单元CTRun提供了CTRunDelegate,也就是当设置了kCTRunDelegateAttributeName过后,CTRun的绘制时所需的参考(长宽等)将可从委托中获取,我们即可通过此方法实现图片的绘制。在需要绘制图片的位置,提前预留空白占位。
CTRun有几个委托用以实现CTRun的几个参数的获取。

文字的绘制只需要知道文字的大小就够了,而图片的绘制不一样,需要知道图片的坐标,高度和宽度。在CoreText中,我们可以把插入的图片当做一个特殊的CTRun,通过delegate来设置图片的宽度和高度,这样就解决了图片的高度和宽度问题,但是CoreText不会自动的对图片进行绘制,因此需要我们自己找到图片的显示位置(原点坐标),然后自己进行绘制

五、图片点击事件

CoreText就是将内容绘制到画布上,自然没有事件处理,我们要实现图片与链接的点击效果就需要使用触摸事件了。当点击的位置在图片的Rect中,那我们做相应的操作即可,所以基本步骤如下:

记录所有图片所在画布中作为一个CTRun的位置 -> 获取每个图片所在画布中所占的Rect矩形区域 -> 当点击事件发生时,判断点击的点是否在某个需要处理的图片Rect内。
六、链接点击事件
七、AzzttribuedString

AttribuedString对象包含很多的属性,每一个属性都有其对应的字符区域,使用NSRange来进行描述的。

1、用NSDictionary来存放属性值
2、对range内的字符指定属性
3、对于控件上字符都是通过NSTextStorage实例来存储
4、isEqual比较每个字符以及他们的属性来判等
5、和CFAttributedStringRef无缝桥接
上一篇 下一篇

猜你喜欢

热点阅读