本文通过 3 个例子,继续讲述 Core Text 实践上的思路
前文已经实现了控制每个字的位置,
Core Text 实践:自定义每个字的位置
一个格子,一个字
一个字,一个富文本,一个 CTLine,
先画背景图,再画文字,即可
例子一,在前文的基础上,实现自动换行
将原本的两行 “椿\n赤い椿”,
转换为一行 “椿 赤い椿”,
剩下的,就容易了
把每个字,转化为格子,
遇到空格,就跳过
模型处理
模型长这样,
struct Coupling: Decodable {
var string: String
let type: Int
}
数据是这样一个 list, [Coupling]
现在要做合并,
能够放在一行,就放在一行,
不能够放在一行,就换行
数据样式: 标题 , 很多格子,标题,其他(略)
// 这个是 result
var info = [Coupling]()
// 放置当前的文本
var current: Coupling?
for jujube in list{
switch jujube.type{
case 4:
// 格子
// 拿到当前的格子,看情况
if var c = current{
// 拼接当前和下一个,看看效果
let reduceTmp = c.string + " " + jujube.string
// 超过一行
if reduceTmp.fourW >= TextContentConst.widthInUse{
// 就添加当前
info.append(c)
// 更新当前文本
current = jujube
}
else{
// 不超过一行,就使用添加过的
c.string = reduceTmp
current = c
}
}
else{
// 进入一组格子时,取第一个
current = jujube
}
default:
// 标题
if let c = current{
info.append(c)
current = nil
}
info.append(jujube)
}
}
// 补余
// 最后的几个,可能不超过一行
if let c = current{
info.append(c)
}
renderGrid = info
这样,文本就转化好了
绘制部分
func drawGrips(m info: TxtRenderInfo, lnH lnHeight: CGFloat, index i: Int, dB startIdx: Int, lineOrigin lnOrigin: CGPoint, context ctx: CGContext, lnAscent lineAscent: CGFloat) -> CGFloat{
// 拿到当前这一句
let content = info.strs[i - startIdx]
let glyphCount = content.count
var frameImg = TextContentConst.fBgTypoImg
let lnOffsset = (TextContentConst.padding - lnHeight) * 0.5
var lineOrigin = lnOrigin
lineOrigin.y -= lnOffsset
var textP = lineOrigin
// 处理每一个字
for idx in 0..<glyphCount{
// 一个字,一个 CTLine
let pieX = String(content[idx])
let ln = CTLineCreateWithAttributedString(pieX.word)
let lnSize = ln.lnSize
let typeOriginX = TextContentConst.padding * CGFloat(idx + 1)
// 文本的原点 x 值 = 图片原点 x 值 + 偏移
textP.x = typeOriginX + (TextContentConst.padding - lnSize.width) * 0.5
ctx.textPosition = textP
// 背景图的 frame
frameImg.origin.x = typeOriginX
frameImg.origin.y = lineOrigin.y + lineAscent - TextContentConst.fBgTypoImg.size.height + TextContentConst.offsetP.y
if pieX != " "{
// 不是空格
// 绘制背景图
bgGrip?.draw(in: frameImg)
// 绘制文本
CTLineDraw(ln, ctx)
}
}
return lnOffsset
}
github repo
例子 2, 字级别的控制 A
每一行,都有合适的间距
从整个的富文本,到 CTFrame,
从 CTFrame,拆出 CTLine,
一部分 CTLine,拆出 CTRun ( 就是左边格子,右边一行文本 )
只能拿到行号
得到的数据如下: 效果比较丰富
拼音距离格子,8 pt
格子 + 文本
一行文本,都是格子
上面的规则,应用到那些 CTLine,
需要建立一个映射 map,
把 CTLine 的行号,对应的格式记录下来
// 拼音距离格子,8 pt
var pronounceY = [Int]()
// 格子 + 文本, 带拼音
var pairs = [Int]()
// 格子 + 文本, 不带拼音
var wArr = [Int]()
// 一整行格子列表的,内容
var strs = [String]()
let cnt = list.count
// 对于每一段内容
var i = 0
// 对于每一个行
var index = 0
// 一整行格子列表的,初始行号
var startIdx: Int? = nil
while i < cnt {
let ri = list[i]
switch ri.type {
case 1:
// 格子 + 文本, 带拼音
// 占据两行
index += 2
pronounceY.append(index)
case 3:
// 格子 + 文本, 不带拼音
index += 1
wArr.append(index)
case 4:
// 一整行格子列表
index += 1
if startIdx == nil{
startIdx = index
}
pairs.append(index)
strs.append(ri.string)
default:
// 拼音部分
index += 1
}
i += 1
}
行号 index, 先 + 1 / + 2, 再添加
因为前面还有一个 title, title 和 body 是分开的,
绘制部分
ctx.textPosition = lineOrigin
if info.contains(pair: i){
// 绘制格子 + 文本, 带拼音,
// 还有,绘制格子 + 文本, 不带拼音
drawPairs(context: ctx, ln: line, startPoint: lineOrigin, ascent: lineAscent)
}
else if info.phraseY.contains(i), let startIdx = info.startIdx{
// 绘制一整行格子列表
let lnHeight = lineAscent + lineDescent + lineLeading
lastY -= drawGrips(m: info, lnH: lnHeight, index: i, dB: startIdx, lineOrigin: lineOrigin, context: ctx, lnAscent: lineAscent)
}
else{
// 绘制拼音
CTLineDraw(line, ctx)
}
github repo
例子 3, 字级别的控制 B
每一个字符,都添加边框
分为两类
- 一行字符,都添加边框
都给画了,每一个字符的边框, shallOmit = true
从每一个 CTLine, 获取其所有的 CTRun,
拿到 CTRun 的所有的字形 glyph,
字形 glyph 的边框位置,是这一行的起始位置 lineOrigin + 这个字在这一行中的位置 point + 这个字本身的位置 box.origin
func drawBorders(context ctx: CGContext, ln line: CTLine, lnOrigin lineOrigin: CGPoint, omit shallOmit: Bool = true){
ctx.setStrokeColor(UIColor.green.withAlphaComponent(0.6).cgColor)
var notOmit = shallOmit
// line 中每个 CTRun
for run in line.glyphRuns{
if notOmit{
let font = run.font
let glyphPositions = run.glyphPositions
let glyphs = run.glyphs
let glyphsBoundingRects = font.boundingRects(of: glyphs)
// CTRun 中每个 glyph
for k in 0..<glyphPositions.count {
let point = glyphPositions[k]
var box = glyphsBoundingRects[k]
box.origin = box.origin + point + lineOrigin
ctx.stroke(box)
}// for k
}
notOmit = true
}//for run
}
- 格子 + 一行字
右边的一行字,都添加边框
shallOmit = false
, 忽略掉第一个字符的边框