利用Core Text进行文本高亮

作者: ameerkat | 来源:发表于2017-03-13 11:45 被阅读231次
    点选文字高亮实现效果.png

    之前实现了一个在UILabel上点击文字,实现文字高亮的效果。实际上是利用 Core Text实现的:用Core Text的相关方法利用文字的CFAttributedString(富文本)属性重写UILabeldrawrect:方法,每次点击文本后改变CFAttributedString属性并用Core Text重绘UILabel,先来看一下CoreText

    CoreText

    框架层次

    Core Text is an advanced, low-level technology for laying out text and handling fonts. Core Text works directly with Core Graphics (CG), also known as Quartz, which is the high-speed graphics rendering engine that handles two-dimensional imaging at the lowest level in OS X and iOS.

    官方文档中提到Core Text是一个底层的负责文字布局,处理字体的框架,直接与Core Graphics交互。Core Text实际上是C实现的,所以使用起来非常灵活,并且可以直接对context进行绘制。

    相关框架层次结构.png

    从框架的层次结构上看,可以证明CoreText相当底层,利用Core Text实现的功能适用于以他为底层的所有控件。

    绘制原理

    Core Text布局引擎结构.png

    如图是Core Text Object的层次结构。

    CTFramesetter:层次结构中的顶层,接受一个attributed string和一个 graphics path作为输入 , framesetter 可以生成 frames of text (CTFrameRef)。 每个 CTFrame object 代表一个段落。CTFramesetter在生成frames的时候会用到typesetter object (CTTypesetterRef)framesetter负责把段落的风格应用到frame 上,typesetter负责把富文本中的字符转换为图形化的字形并填入组成frameline object(CTLine)中。

    CTLine:每一个 CTFrame object 都包含一个或多个line (CTLine) objects,每一个line代表一行文字。

    CTRun:每一个 CTLine object 包含一个 glyph run (CTRun) objects 数组。一个 glyph run 是一系列连续的共享相同attributes and directionglyph(字形)。一个CTLine object可能包含一个或多个CTRun object

    由于CTFrame可以直接把自己绘制到graphics context上,我们就可以通过改变 NSAttributedString object的属性来改变CTFrame object绘制出来的样子。

    实现点击高亮

    获取点击位置

    // 获取点击到的Character在整个段落文字中的Index
    func indexOf(point: CGPoint) -> CFIndex{
            // 反转坐标
            let reversedPoint = CGPoint(x: point.x, y: self.bounds.maxY - point.y)
            // 获取段落的lines
            let lines = CTFrameGetLines(mCTFrame!) as NSArray
            // 获取lines的origin坐标
            var originsArray = [CGPoint] (repeating: .zero, count: lines.count)
            CTFrameGetLineOrigins(mCTFrame!, CFRangeMake(0, lines.count), &originsArray)
            
            for i in 0..<lines.count {
                if(reversedPoint.y > originsArray[i].y) {    // 遍历到点击的行
                    let line = lines.object(at: i) as! CTLine
                    // 用对应的line和点击的坐标获取点击的character的Index
                    return CTLineGetStringIndexForPosition(line, reversedPoint)
                }
            }
            return 0
        }
    

    可以通过获得的Index计算对应String的Index Range。

    关于func CTLineGetStringIndexForPosition(_ line: CTLine, _ position: CGPoint) -> CFIndex:这个方法是通过在这一行中分析点击的character是由哪个typesetter创建,并分析出其对应的字形的位置。

    添加attribute属性

    override func draw(_ rect: CGRect) {
            let context = UIGraphicsGetCurrentContext()
            context?.convertCoordinateSystem(view: self)
            
            let mutablePath = UIBezierPath(rect: rect)
            let mutableAttributeString =  NSMutableAttributedString(string: self.text!)
            mutableAttributeString.addAttribute(NSFontAttributeName, value: ARLTICLE_CONTENT_FONT, range: NSMakeRange(0, mutableAttributeString.length))
            
            // 文本排版格式
            let style = NSMutableParagraphStyle()
            style.lineSpacing = LINE_SPACING
            style.alignment = .justified
            mutableAttributeString.addAttribute(NSParagraphStyleAttributeName, value: style, range: NSMakeRange(0, mutableAttributeString.length))
            
            /*
             *  此处省略计算高亮range相关代码
             */
                    
            // range:高亮String的Range
            mutableAttributeString.addAttributes([NSBackgroundColorAttributeName: MAIN_COLOR, NSForegroundColorAttributeName: HIGHT_LIGHT_TEXT_COLOR],range:range)
                }
                highlightWordsLoaction.removeLast()
                allowSelectWord = true
            }
            
            let framesetter = CTFramesetterCreateWithAttributedString(mutableAttributeString)
            // mCTFrame之前已经声明
            mCTFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, mutableAttributeString.length), mutablePath.cgPath, nil)
            CTFrameDraw(mCTFrame!, context!)
        }
    
    

    最后

    没填的坑:从第二段开始高亮位置向前偏移,并且UILabel高度计算不准确,填完补充解决办法。

    相关文章

      网友评论

        本文标题:利用Core Text进行文本高亮

        本文链接:https://www.haomeiwen.com/subject/etlfnttx.html