共计 3551 个字符,预计需要花费 9 分钟才能阅读完成。
本文应用 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
调小了,这一帧,就像一幅画被压缩了
默认是,这一帧,就像一幅很长的画,被截取了有字的局部,失常展现
github repo
正文完