一、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无缝桥接
网友评论