iOS高阶UI相关iOS开发技术分享iOS Developer

利用Core Text进行文本高亮

2017-03-13  本文已影响231人  ameerkat
点选文字高亮实现效果.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高度计算不准确,填完补充解决办法。

上一篇下一篇

猜你喜欢

热点阅读