CoreText是Mac OS和iOS系统中处理文本的low-level API, 不管是使用OC还是swift, 实际我们使用CoreText都还是间接或直接使用C语言在写代码。CoreText是iOS和Mac OS中文本处理的根基, TextKit和WebKit都是构建于其上。
1.在使用CoreText编写代码之前, 需要先了解一些基础知识。下图是CoreText的基础框架
2.文字的样式包括很多, 而每个字符的显示要归功于字体, 而字体包括很多基础知识, 比如磅值, 样式, 基线, 连字等等, 这里就不做更多介绍, 推荐两篇文章阅读。
CoreText基础概念
CoreText入门
在这里, 贴出CoreText基础概念文中关于字体结构的图(图片版权归此文作者)
新建一个UIView, 在view的drawRect函数中按步骤写入下面的代码
let context = UIGraphicsGetCurrentContext()
CGContextSetTextMatrix(context, CGAffineTransformIdentity)
CGContextTranslateCTM(context, 0, self.bounds.size.height)
CGContextScaleCTM(context, 1.0, -1.0)
let path = CGPathCreateWithRect(self.bounds, nil)
let attrString = NSMutableAttributedString(string: "Hello CoreText")
let framesetter = CTFramesetterCreateWithAttributedString(attrString)
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, attrString.length), path, nil)
CTFrameDraw(frame, context!)
绘制的步骤完成了, 然后在ViewController里面将此view加入到ViewController中, 记得将view的背景色设置为白色, 那么效果就应该如下了
文字绘制出来了, 这就是CoreText使用最基本的步骤了。
上面简单的绘制步骤中, 最后一步绘制frame, 是将整个frame当做一块绘制, 至于什么换行, 行中的样式什么的都是系统自己决定了。在开始之前, 我们将这个绘制frame改成我们自己一行一行, 甚至一个run一个run的绘制
// 1.获得CTLine数组
let lines = CTFrameGetLines(frame)
// 2.获得行数
let numberOfLines = CFArrayGetCount(lines)
// 3.获得每一行的origin, CoreText的origin是在字形的baseLine处的, 请参考字形图
var lineOrigins = [CGPoint](count: numberOfLines, repeatedValue: CGPointZero)
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), &lineOrigins)
// 4.遍历每一行进行绘制
for index in 0..let origin = lineOrigins[index]
// 参考: http://swifter.tips/unsafe/
let line = unsafeBitCast(CFArrayGetValueAtIndex(lines, index), CTLine.self)
// 设置每一行的位置
CGContextSetTextPosition(context, origin.x, origin.y)
// 开始一行的绘制
CTLineDraw(line, context)
}
将最后一步改成按行绘制, 最终得到的效果也和按frame绘制一样的, 接下来看下按Run绘制
// 画一行
func drawLine(line: CTLine, context: CGContext) {
let runs = CTLineGetGlyphRuns(line) as Array
runs.forEach { run in
CTRunDraw(run as! CTRun, context, CFRangeMake(0, 0))
}
}
}
用此函数替换CTLineDraw(line, context)这一句就可以了, 效果也如上面。
那么接下来实现一个简单的富文本Label, 将上面的view改名为TULabel
var attributedText: NSAttributedString?
let attributedText = NSMutableAttributedString(string: ...)
// CoreText支持的属性
// 字体颜色
attributedText.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: NSMakeRange(0, 10))
// 下划线
let underlineStyles = [NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue,
NSUnderlineColorAttributeName: UIColor.orangeColor()]
attributedText.addAttributes(underlineStyles, range: NSMakeRange(10, 10))
// 字体
attributedText.addAttribute(NSFontAttributeName, value: UIFont.boldSystemFontOfSize(50), range: NSMakeRange(20, 10))
// 描边(Stroke):组成字符的线或曲线。可以加粗或改变字符形状
let strokeStyles = [NSStrokeWidthAttributeName: 10,
NSStrokeColorAttributeName: UIColor.blueColor()]
attributedText.addAttributes(strokeStyles, range: NSMakeRange(40, 20))
// 横竖文本
attributedText.addAttribute(NSVerticalGlyphFormAttributeName, value: 0, range: NSMakeRange(70, 10))
// 字符间隔
attributedText.addAttribute(NSKernAttributeName, value: 5, range: NSMakeRange(90, 10))
// 段落样式
let paragraphStyle = NSMutableParagraphStyle()
//对齐模式
paragraphStyle.alignment = .Center
//换行裁剪模式
paragraphStyle.lineBreakMode = .ByWordWrapping
// 行间距
paragraphStyle.lineSpacing = 5.0
// 字符间距
paragraphStyle.paragraphSpacing = 2.0
attributedText.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: NSRange(location: 0, length: attributedText.length))
此时, 你就会看到如下效果
// 画一行
func drawLine(line: CTLine, context: CGContext) {
let runs = CTLineGetGlyphRuns(line) as Array
runs.forEach { run in
CTRunDraw(run as! CTRun, context, CFRangeMake(0, 0))
// 获得run的所有样式
let attributes = CTRunGetAttributes(run as! CTRun) as NSDictionary
// 判断是run是否含有删除线样式
if nil != attributes[NSStrikethroughStyleAttributeName] {
// 开始画删除线
drawStrikethroughStyle(run as! CTRun, attributes: attributes, context: context)
}
}
}
// 画删除线, 这里涉及到字体相关知识, 请参考第二节, 画删除线实际画在字的中间, 而字体的高度不一样, 实际是画在x高度的一半位置
func drawStrikethroughStyle(run: CTRun, attributes: NSDictionary, context: CGContext) {
// 1.获取删除线样式
let styleRef = attributes[NSStrikethroughStyleAttributeName]
var style: NSUnderlineStyle = .StyleNone
CFNumberGetValue(styleRef as! CFNumber, CFNumberType.SInt64Type, &style)
// 如果定义为none, 就不用画了
guard style != .StyleNone else {
return
}
// 2.获得画线的宽度
var lineWidth: CGFloat = 1
if (style.rawValue & NSUnderlineStyle.StyleThick.rawValue) == NSUnderlineStyle.StyleThick.rawValue {
lineWidth *= 2
}
CGContextSetLineWidth(context, lineWidth)
// 3.获取画线的起点
var firstPosition = CGPointZero
let firstGlyphPosition = CTRunGetPositionsPtr(run)
if nil == firstGlyphPosition {
let positions = UnsafeMutablePointer<CGPoint>.alloc(1)
positions.initialize(CGPointZero)
CTRunGetPositions(run, CFRangeMake(0, 0), positions)
firstPosition = positions.memory
positions.destroy()
} else {
firstPosition = firstGlyphPosition.memory
}
// 4.我们要开始画线了
CGContextBeginPath(context)
// 5.获取定义的线的颜色, 默认为黑色
let lineColor = attributes[NSStrikethroughColorAttributeName]
if nil == lineColor {
CGContextSetStrokeColorWithColor(context, UIColor.blackColor().CGColor)
} else {
CGContextSetStrokeColorWithColor(context, (lineColor as! UIColor).CGColor)
}
// 6.字体高度, 中间位置为x高度的一半
let font = attributes[NSFontAttributeName] ?? UIFont.systemFontOfSize(UIFont.systemFontSize())
var strikeHeight: CGFloat = font.xHeight / 2.0 + firstPosition.y
// 多行调整
let pt = CGContextGetTextPosition(context)
strikeHeight += pt.y
// 画线的宽度
let typographicWidth = CGFloat(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), nil, nil, nil))
// 7.开始画线
CGContextMoveToPoint(context, pt.x + firstPosition.x, strikeHeight)
CGContextAddLineToPoint(context, pt.x + firstPosition.x + typographicWidth, strikeHeight)
CGContextStrokePath(context)
}
// 删除线
let strikethroughStyle = [NSStrikethroughStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue,
NSStrikethroughColorAttributeName: UIColor.cyanColor()]
attributedText.addAttributes(strikethroughStyle, range: NSMakeRange(150, 20))
这样就实现了删除线样式效果了
至此, 删除线的样式就完成了, 其他样式将可能在下一篇CoreText文章中实现。
源码在此, 请参考源码中的CoreText/1文件夹!!!
参考:
CoreText基础概念
CoreText入门
Nimbus
本文由啸寒原创, 转载请注明出处!!!