本文应用 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
调小了,这一帧,就像一幅画被压缩了
默认是,这一帧,就像一幅很长的画,被截取了有字的局部,失常展现