实现下面的效果
可以使用很多控件,
左边红框内,一个 UILabel;
右边红框内,一个 UILabel
很多个 UILabel 码起来
使用 CoreText 可以用一个控件,实现绘制
思路:
先把原文,转化为下面的
再把第二张,转化为最初的效果
先要弄出一个类似结构的富文本
保证每一行的 Y 坐标,可控
( 保证非右边红框的内容,与最终一致)
( 保证右边红框的内容,其行数和 y 坐标,与最终一致)
- 富文本,转化为 CTFrame 一, 出现第二张图,( 把右边红框内的文本,处理掉 )
关键是,把右边红框内的富文本,合理拆分出来
CTFrame 一,宽度 = 右边红框内的富文本的宽度 + 50
- 右边红框富文本,转化为 CTFrame 二 , 糊到上一步空出的地方
右边红框内的文字,自然不能用 CTFrame 一
一个右边的红框,就是一个 CTFrame 二
给一个字符串,怎么知道它有多少行
字符串,转化为富文本,
富文本,转化为 CTLine,
拿到 CTLine 的宽度 lineWidth,
知道文本框的宽度 containerWidth,
行数 = Int(ceil(lineWidth / containerWidth))
实现:
例子数据
[
{
"string" : "世界が終るまでは",
"type" : 6
},
{
"string" : "世界が終わる前に 聞かせておくれよ",
"title" : "歌詞:",
"type" : 7
},
{
"string" : "在世界尽头之前告诉我",
"title" : "注釈:",
"type" : 8
},
{
"string" : "世界が終わるまでは 離れる事もない . そう願ってた 幾千の夜と . 戻らない時だけが 何故輝いては",
"title" : "歌詞:",
"type" : 7
},
{
"string" : "我要走到世界尽头 . 我希望成千上万的夜晚 . 为什么只在不回来时才闪耀",
"title" : "注釈:",
"type" : 8
}
]
转化为
用于右边红框内的占位,和非右边红框区域的渲染
- 右边红框内的占位
拆分技术,前面有写
从内容 string,拆分出 subList
▿ 0 : Coupling
- string : "世界が終るまでは"
- type : 6
▿ 1 : Coupling
- string : "世界が終わる前に 聞かせておくれよ"
- type : 7
▿ title : Optional<String>
- some : "歌詞:"
▿ subList : Optional<Array<String>> // 用于占位, 一个字符串,拆出了两行
▿ some : 2 elements
- 0 : "ha ha ha"
- 1 : "ha ha ha"
▿ 2 : Coupling
- string : "在世界尽头之前告诉我"
- type : 102
▿ title : Optional<String>
- some : "注釈:"
▿ 3 : Coupling
- string : "世界が終わるまでは 離れる事もない . そう願ってた 幾千の夜と . 戻らない時だけが 何故輝いては"
- type : 7
▿ title : Optional<String>
- some : "歌詞:"
▿ subList : Optional<Array<String>> // 用于占位, 一个字符串,拆出了3行
▿ some : 3 elements
- 0 : "ha ha ha"
- 1 : "ha ha ha"
- 2 : "ha ha ha"
▿ 4 : Coupling
- string : "我要走到世界尽头 . 我希望成千上万的夜晚 . 为什么只在不回来时才闪耀"
- type : 8
▿ title : Optional<String>
- some : "注釈:"
▿ subList : Optional<Array<String>> // 用于占位, 一个字符串,拆出了3行
▿ some : 3 elements
- 0 : "ha ha ha"
- 1 : "ha ha ha"
- 2 : "ha ha ha"
和红框内绘制信息
struct Renderer{
// 红框内,每一段的绘制信息
let paragraph: [ParagraphRenderer]
}
struct ParagraphRenderer{
// 对应实际绘制的开始行
let lineIdx: Int
// 右边红框对应的文本
let content: String
// 右边红框对应的行数
let cnt: Int
// 左框对应的文本
let t: String
// 确定样式
let beBlack: Bool
// 右边红框用于绘制的信息
var lines = [CTLine]()
}
模型计算部分,见底部 github repo
计算思路,可参考之前的博客,见底部
绘制
// y 坐标控制
// Core Text 坐标,原点在左下,
// 先对 UIKit 坐标系翻转,
// lastY, 是行距的累计
var lastY: CGFloat = 0
var index = 0
let limit = info.paragraph.count
// 段落开始行距的记录
var startIndex: Int? = nil
for (i,line) in lines.enumerated(){
var lineAscent:CGFloat = 0
var lineDescent:CGFloat = 0
var lineLeading:CGFloat = 0
CTLineGetTypographicBounds(line , &lineAscent, &lineDescent, &lineLeading)
var lineOrigin = originsArray[i]
lineOrigin.x = TextContentConst.padding + lineOrigin.x
// ...
// y 坐标控制
let yOffset = lineOrigin.y - lineDescent - 20
if i == info.lineIndex{
ctx.draw(line: yOffset)
}
ctx.textPosition = lineOrigin
// 绘制记录,
// 如果绘制了红框内的内容,
// 就不要绘制 CTFrame 一 的内容了
var toDraw = false
// 继续处理段落的剩余行
if let f = startIndex{
if i >= f{
// 段落画完了
startIndex = nil
}
else{
// 绘制段落的剩余行
let biscuit = info.paragraph[index - 1]
ctx.textPosition = CGPoint(x: lineOrigin.x + TextContentConst.indentSecond, y: lineOrigin.y)
// startIndex = i + biscuit.cnt 的时机,
// biscuit.cnt + i - f = 初始行
// i 增加,对应段落的剩余行
CTLineDraw(biscuit.lines[biscuit.cnt - (f - i)], ctx)
toDraw = true
}
}
// 逻辑的入口,
// 找到了第一段的第一行
if index < limit, info.paragraph[index].lineIdx == i{
let biscuit = info.paragraph[index]
// 左边红框的信息
let attributedStr: NSAttributedString = biscuit.t.seven(toBreak: false)
let ln = CTLineCreateWithAttributedString(attributedStr)
// 画左边红框
CTLineDraw(ln, ctx)
ctx.textPosition = CGPoint(x: lineOrigin.x + TextContentConst.indentSecond, y: lineOrigin.y)
// 画右边红框的第一行
CTLineDraw(biscuit.lines[0], ctx)
toDraw = true
startIndex = i + biscuit.cnt
index += 1
}
if toDraw == false{
CTLineDraw(line, ctx)
}
}
模型优化
case 7: // 只是一个记号
// ...
let attributedStr = NSAttributedString()
let lnTwo = CTLineCreateWithAttributedString(attributedStr)
let w = lnTwo.lnSize.width
let cnt = w / TextContentConst.widthBritain
var m = model
let len = Int(ceil(cnt))
if len > 1{
// 右边红框超过一行,放在 Frame 2
(1..<len).forEach {
acht.append($0 + enLineIdx)
}
m.subList = [String](repeating: product, count: len)
infoEn.append(ParagraphRenderer(lineIdx: enLineIdx, content: model.string, cnt: len, t: m.title ?? "", beBlack: true))
}
else{
// 右边红框等于一行,放在 Frame 1 中
m.type = 101 // 只是一个记号
}
info.append(m)
enLineIdx += len