本文使用 Core Text 实现这个效果,滚动文本,中间那一行变红
Core Text 实践 +:文字随心所欲摆放
前文等,已经实现了,
使用 CoreText, 可以自定义的控制每一行的位置
思路
滚动的时候,使用一个计时器,不停的重绘,setNeedsDisplay()
找出中间的那一行,变红
其它行,维持原样
实现
父视图, 滚动视图
// 滚动视图
class ReadScrollV: UIScrollView {
// 滚动视图,放置一个文本绘制视图
fileprivate lazy var ccc = TxtViewCustom()
var timer: Timer?
func setup(){
let t: TimeInterval = 0.1
timer = Timer.scheduledTimer(timeInterval: t , target: self, selector: #selector(ReadScrollV.loops), userInfo: nil, repeats: true)
timer?.fire()
if timer != nil{
RunLoop.main.add( timer! , forMode: RunLoop.Mode.common)
}
}
@objc func loops(){
if isDragging || isTracking{
// 滚动的时候,重绘
ccc.criteria = contentOffset.y + TxtCustomConst.kLnTop
ccc.setNeedsDisplay()
}
}
}
子视图,文本绘制
- 找到需要高亮的那一行 CTLine
直接操作,改颜色刷新,暂无好办法
- 找到高亮行 CTLine,
通过 CTLineGetStringRange
,获取在那一帧 frame 的范围,
再操作那一帧 frame 原始文本,
绕一圈,就可以高亮了
class TxtViewCustom: UIView{
var frameRef:CTFrame? // 文本,转化为一帧
var criteria = TxtCustomConst.kLnTop // 滚动的时候,在变
var contentPageX: NSAttributedString? // 富文本
// 绘制文本
override func draw(_ rect: CGRect){
guard let ctx = UIGraphicsGetCurrentContext(), let f = frameRef, let lineIndex = txtRenderX else{
return
}
let xHigh = bounds.size.height
ctx.textMatrix = CGAffineTransform.identity
ctx.translateBy(x: 0, y: xHigh)
ctx.scaleBy(x: 1.0, y: -1.0)
guard let lines = CTFrameGetLines(f) as? [CTLine] else{
return
}
let lineCount = lines.count
guard lineCount > 0 else {
return
}
// 前面都是熟悉的老套路,
// 翻转坐标系,从文本帧中,取出每一行
var originsArray = [CGPoint](repeating: CGPoint.zero, count: lineCount)
//用于存储每一行的坐标
CTFrameGetLineOrigins(f, CFRangeMake(0, 0), &originsArray)
var final: CGFloat = 0
var first: CGFloat? = nil
var lastY: CGFloat = -16
var toRender:Bool? = 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 = TxtCustomConst.padding + lineOrigin.x
lineOrigin.y += lastY
if i == lineIndex{
let yOffset = lineOrigin.y - lineDescent - 20
ctx.line(draw: yOffset)
}
if i <= lineIndex{
lastY -= 11
}
else{
lastY -= 7
}
ctx.textPosition = lineOrigin
// 前面是一些,
// 每一行的 y 坐标控制
if first == nil{
first = lineOrigin.y
}
let typoH = lineAscent + lineDescent
final = lineOrigin.y - typoH
let oneX: CGFloat = first ?? 0
// 这个判断依据,靠经验
// 效果还可以
if toRender == nil, oneX - final + typoH * 2 - 10 >= criteria{
toRender = true
// 绘制两条辅助线
ctx.line(red: final + typoH * 2)
ctx.line(red: final )
}
if let re = toRender, re{
// 高亮绘制
toRender = false
// 找到了所在行
let lineRange = CTLineGetStringRange(line)
let range = NSMakeRange(lineRange.location == kCFNotFound ? NSNotFound : lineRange.location, lineRange.length)
// 找到对应的文本范围
if let content = contentPageX{
let sub = content.string[range.location..<(range.location + range.length)]
let new = String(sub)
// 新建文本,新建高亮行
let lnTwo = CTLineCreateWithAttributedString(new.highLn)
// 高亮绘制
CTLineDraw(lnTwo, ctx)
}
}
else{
// 普通绘制
CTLineDraw(line, ctx)
}
}
// ...
}
}
补充:
// 滚动视图
class ReadScrollV: UIScrollView {
// 滚动的时候,需要高亮的那一行,在变动
// 需要加上 contentOffset.y
@objc func loops(){
ccc.criteria = contentOffset.y + TxtCustomConst.kLnTop
ccc.setNeedsDisplay()
}
}
复习
为了顺利绘制那一帧,
将富文本传入子视图,
子视图使用固定的文本宽,计算出一个合适的文本高
合适的文本高乘上安全系数 ( 这里取 3 ),
父视图设置子视图的 frame 和自个的 contentSize
再去绘制
class ReadScrollV: UIScrollView {
fileprivate lazy var ccc = TxtViewCustom()
var s: CGSize?
var contentPage: NSAttributedString?{
didSet{
/*
将富文本传入子视图,
子视图使用固定的文本宽,计算出一个合适的文本高
合适的文本高乘上安全系数 ( 这里取 3 )
*/
ccc.contentPageX = contentPage
s = ccc.s
/*
父视图设置子视图的 frame 和自个的 `contentSize`
再去绘制
*/
if let sCont = s{
let f = CGRect(x: 0, y: 0, width: UI.std.width, height: sCont.height)
ccc.frame = f
contentSize = f.size
}
ccc.setNeedsDisplay()
}
}
}
绘制完成后,拿到子视图中 func draw(_ rect: CGRect)
方法中,
计算出来的切合实际的高度,调整父视图的 contentSize
extension ReadScrollV: DrawDoneProxy{
func done(height h: CGFloat){
let cccS = ccc.frame.size
contentSize = CGSize(width: cccS.width, height: max(h + 400, UI.std.height - CGFloat(64 * 2) - 40 + 8))
}
}
此时不能再,调整子视图的 frame
调小了,这一帧,就像一幅画被压缩了
默认是,这一帧,就像一幅很长的画,被截取了有字的部分,正常展示