本文应用 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