天天看点

iOS 开发 富文本详解之TextKit详解

textkit结构

iOS 开发 富文本详解之TextKit详解

textkit使用步骤

#Mark - 1. 自定义label  --class CZLabel: UILabel---四个属性
//1.属性文本存储
private lazy var textStorage = NSTextStorage()
//2.负责文本字形布局对象
private lazy var layoutManager = NSLayoutManager()
//3.设定文本绘制的范围
private lazy var textContainer = NSTextContainer()
//4.属性数组,保存匹配的范围
private lazy var linkRanges = [NSRange]()

#Mark - 2. 重新init方法-- override init(frame: CGRect) {}
//0.开启用户交互
userInteractionEnabled = true
//1.textStorage接管label的属性
if let attributedText = attributedText {}
//2.设置对象关系
textStorage.addLayoutManager(layoutManager)
layoutManager.addTextContainer(textContainer)

#Mark - 3. 外界给label的text属性赋值  label.text = @"@好友,#健康#,....."
//重写属性的text方法--一旦label里的内容发生变化,就可以让textStorage相应变化
//1.段落处理--1.范围  2.属性  3.段落样式
let attrStringM = addLineBreak(attributedText!)
//2.正则匹配--1.清空原有  2.匹配范围  3.创建正则  4.匹配  5.遍历匹配结果,添加到属性数组
regexLinkRanges(attrStringM)
//3.连接颜色设置---1.范围  2.属性  3.添加颜色  4.遍历属性数组,改变颜色
addLinkAttribute(attrStringM)
//4.添加到textStorage
textStorage.setAttributedString(attrStringM)
//5.重新绘制
setNeedsDisplay()

#Mark - 4. textStorage字形和属性发生变化时,通知NSLayoutManager重新布局文本
//MARK:3.设置布局--制定文本绘制区域
override func layoutSubviews() {
    super.layoutSubviews()
    //制定文本绘制区域
    textContainer.size = bounds.size
}

#Mark - 5. 绘制textStorage的文本内容--不能调用super
override func drawTextInRect(rect: CGRect) {
    let range = NSMakeRange(, textStorage.length)
    //Glyphs--字形---CGPoint()从原点绘制,也就是右上角
    layoutManager.drawGlyphsForGlyphRange(range, atPoint: CGPoint(x: ,y: ))
}

#Mark - 6. 用户点击事件交互
//0.懒加载@ # URL的匹配的正则法则 三个属性数组
三步法:正则表达式  创建正则  匹配  便利匹配结果,添加到属性数组
 //1.获取用户点击的位置
let location = touches.first?.locationInView(self)
//2.获取当前点中字符的索引
let index = layoutManager.glyphIndexForPoint(location, inTextContainer: textContainer)
//3.判断index在哪个标记的range 范围上
for range in atRange ?? [] {
    if NSLocationInRange(index, range) {
        let strSub = (textStorage.string as NSString).substringWithRange(range)
        //进行结果处理
    }
}
           

Swift使用

import UIKit

class ZYLabel: UILabel {        //attributedText富文本

    //MARK:2.重写属性text方法,可以在ViewController里给文本赋值
    //一旦label里的内容发生变化,就可以让textStorage相应变化
    override var text:String? {
        didSet {
            if attributedText == nil {
                return
            }
            //换行处理属性
            let attrStringM = addLineBreak(attributedText!)
            //换行后进行--正则匹配
            regexLinkRanges(attrStringM)
            //换行后进行--连接颜色设置
            addLinkAttribute(attrStringM)
            //添加到textStorage
            textStorage.setAttributedString(attrStringM)
            //重新绘制
            setNeedsDisplay()
        }
    }

    ///MARK: textKit的三个核心对象
    //属性文本存储
    private lazy var textStorage = NSTextStorage()
    //负责文本字形布局对象
    private lazy var layoutManager = NSLayoutManager()
    //设定文本绘制的范围
    private lazy var textContainer = NSTextContainer()
    private lazy var linkRanges = [NSRange]()

    //纯代码接管Label
    override init(frame: CGRect) {
        super.init(frame: frame)
        //0.开启用户交互
        userInteractionEnabled = true

        //1.textStorage接管label的属性
        if let attributedText = attributedText {        //如果原有文本设置了attribute
            textStorage.setAttributedString(attributedText)
        }else if let text = text {      //如果原有文本没有设置attribute
            textStorage.setAttributedString(NSAttributedString(string: text))
        }else {     //如果原有文本为nil
            textStorage.setAttributedString(NSAttributedString(string: ""))
        }

        //2.设置对象关系
        textStorage.addLayoutManager(layoutManager)
        layoutManager.addTextContainer(textContainer)
    }
    //MARK:1.Xib接管Label
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        //0.开启用户交互
        userInteractionEnabled = true

        //1.准备文本内容---textStorage接管label的内容
        if let attributedText = attributedText {        //如果原有文本有attribute属性
            textStorage.setAttributedString(attributedText)
        }else if let text = text {      //如果原有文本没有attribute属性
            textStorage.setAttributedString(NSAttributedString(string: text))
        }else {     //如果原有文本属性为nil
            textStorage.setAttributedString(NSAttributedString(string: ""))
        }

        //2.设置对象关系
        textStorage.addLayoutManager(layoutManager)
        layoutManager.addTextContainer(textContainer)
    }

    /// MARK:2.1.段落样式处理
    private func addLineBreak(attrString: NSAttributedString) -> NSMutableAttributedString {
        let attrStringM = NSMutableAttributedString(attributedString: attrString)
        if attrStringM.length ==  {
            return attrStringM
        }
        //从(0,0)点开始,也就是从text的第一个字符开始
        var range = NSRange(location: , length: )
        var attributes = attrStringM.attributesAtIndex(, effectiveRange: &range)
        var paragraphStyle = attributes[NSParagraphStyleAttributeName] as? NSMutableParagraphStyle

        //设置段落样式--以字符分割,不以单词分割
        if paragraphStyle != nil {
            //ByWordWrapping//按照单词分割换行,保证换行时的单词完整。
            //ByCharWrapping按照字母换行,可能会在换行时将某个单词拆分到两行
            paragraphStyle!.lineBreakMode = NSLineBreakMode.ByCharWrapping
        } else {
            // iOS 8.0 不能直接获取段落的样式
            paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle!.lineBreakMode = NSLineBreakMode.ByCharWrapping
            attributes[NSParagraphStyleAttributeName] = paragraphStyle
            attrStringM.setAttributes(attributes, range: range)
        }
        return attrStringM
    }
    /// MARK:2.2.连接的attribute的颜色设置
    private func addLinkAttribute(attrStringM: NSMutableAttributedString) {
        if attrStringM.length ==  {
            return
        }
        var range = NSRange(location: , length: )
        var attributes = attrStringM.attributesAtIndex(, effectiveRange:  &range)
        attrStringM.addAttributes(attributes, range: range)
        attributes[NSForegroundColorAttributeName] = UIColor.blueColor()
        for range in linkRanges {
            attrStringM.setAttributes(attributes, range: range)
        }
    }
    /// MARK:2.3.正则法则--匹配所有连接颜色:URL,#话题#,@好友---放到一个数组里
    private let patterns = ["((http[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\[email protected]#$%^&*+?:[a-zA-Z0-9]{3}_/=<>]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\[email protected]#$%^&*+?:_/=<>]*)?)", "#.*?#", "@[\\u4e00-\\u9fa5a-zA-Z0-9_-]*"]
    private func regexLinkRanges(attrString: NSAttributedString) {
        //存储所有的匹配结果前,将原来的清空
        linkRanges.removeAll()
        //正则匹配范围--整个label
        let regexRange = NSRange(location: , length: attrString.string.characters.count)
        for pattern in patterns {
            //创建正则
            let regex = try! NSRegularExpression(pattern: pattern, options: .DotMatchesLineSeparators)
            //匹配
            let results = regex.matchesInString(attrString.string, options:NSMatchingOptions(rawValue: ) , range: regexRange)
            for range in results {      //每一种正则法则可能匹配到多个符合要求的对象如@张三  @李四 匹配到两个,结果是个数组
                linkRanges.append(range.rangeAtIndex())
            }
        }
    }

    //MARK:3.设置布局--制定文本绘制区域
    override func layoutSubviews() {
        super.layoutSubviews()
        //制定文本绘制区域
        textContainer.size = bounds.size
    }

    //MARK:4.绘制textStorage的文本内容--不能调用super
    override func drawTextInRect(rect: CGRect) {
        let range = NSMakeRange(, textStorage.length)
        //Glyphs--字形---CGPoint()从原点绘制,也就是右上角
        layoutManager.drawGlyphsForGlyphRange(range, atPoint: CGPoint(x: ,y: ))
    }

    //MARK:5.用户点击事件交互--处理不同匹配内容天转到不同界面
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        //1.获取用户点击的位置--let location: CGPoint?
        guard let location = touches.first?.locationInView(self) else {
            return
        }

        //获取当前点中字符的索引
        let index = layoutManager.glyphIndexForPoint(location, inTextContainer: textContainer)

        //判断index在哪个标记的range 范围上
        for range in atRange ?? [] {
            if NSLocationInRange(index, range) {
                let strSub = (textStorage.string as NSString).substringWithRange(range)
                print(strSub)
            }
        }
        for range in jingRange ?? [] {
            if NSLocationInRange(index, range) {
                let strSub = (textStorage.string as NSString).substringWithRange(range)
                print(strSub)
            }
        }
        for range in urlRange ?? [] {
            if NSLocationInRange(index, range) {
                let strSub = (textStorage.string as NSString).substringWithRange(range)
                NSNotificationCenter.defaultCenter().postNotificationName("webView", object: self, userInfo: ["urlString":strSub])
            }
        }
    }
}

//MARK: 正则表达式处理结果
extension ZYLabel {

    //返回textStorage中的@肝健康公益 的range数组
    var atRange:[NSRange]? {
        //正则表达式[email protected]好友
        let pattern = "@[\u{4e00}-\u{9fa5}]{0,}"
        guard let regx = try? NSRegularExpression(pattern: pattern, options: []) else {
            return nil
        }
        //多重匹配--//let matchs: [NSTextCheckingResult]
        let matchs = regx.matchesInString(textStorage.string, options: [], range: NSRange(location: ,length: textStorage.length))
        //遍历数组
        var ranges = [NSRange]()
        for match in matchs {
            ranges.append(match.rangeAtIndex())
        }
        return ranges
    }

    //返回textStorage中的话题## 的range数组
    var jingRange:[NSRange]? {
        //正则表达式
        let pattern = "#[\u{4e00}-\u{9fa5}]{0,}#"
        guard let regx = try? NSRegularExpression(pattern: pattern, options: []) else {
            return nil
        }
        //多重匹配--//let matchs: [NSTextCheckingResult]
        let matchs = regx.matchesInString(textStorage.string, options: [], range: NSRange(location: ,length: textStorage.length))
        //遍历数组
        var ranges = [NSRange]()
        for match in matchs {
            ranges.append(match.rangeAtIndex())
        }
        return ranges
    }
    //返回textStorage中的URL网址的range数组
    var urlRange:[NSRange]? {
        //正则表达式
        let pattern = "((http[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\[email protected]#$%^&*+?:[a-zA-Z0-9_/=<>]]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\[email protected]#$%^&*+?:_/=<>]*)?)"
        guard let regx = try? NSRegularExpression(pattern: pattern, options: []) else {
            return nil
        }
        //多重匹配--//let matchs: [NSTextCheckingResult]
        let matchs = regx.matchesInString(textStorage.string, options: [], range: NSRange(location: ,length: textStorage.length))
        //遍历数组
        var ranges = [NSRange]()
        for match in matchs {
            ranges.append(match.rangeAtIndex())
        }
        return ranges
    }
}