欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-08-09 18:57:53
textkit结构 textkit使用步骤 #mark - 1. 自定义label --class czlabel: uilabel---四个属性 //1.属性文本存储 private...

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(0, textstorage.length)
    //glyphs--字形---cgpoint()从原点绘制,也就是右上角
    layoutmanager.drawglyphsforglyphrange(range, atpoint: cgpoint(x: 0,y: 0))
}

#mark - 6. 用户点击事件交互
//0.懒加载@ # url的匹配的正则法则 三个属性数组
三步法:1.正则表达式  2.创建正则  3.匹配  4.便利匹配结果,添加到属性数组
 //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 == 0 {
            return attrstringm
        }
        //从(0,0)点开始,也就是从text的第一个字符开始
        var range = nsrange(location: 0, length: 0)
        var attributes = attrstringm.attributesatindex(0, 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 == 0 {
            return
        }
        var range = nsrange(location: 0, length: 0)
        var attributes = attrstringm.attributesatindex(0, 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\\.\\-~!@#$%^&*+?:[a-za-z0-9]{3}_/=<>]*)?)|(www.[a-za-z0-9\\.\\-]+\\.([a-za-z]{2,4})(:\\d+)?(/[a-za-z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)", "#.*?#", "@[\\u4e00-\\u9fa5a-za-z0-9_-]*"]
    private func regexlinkranges(attrstring: nsattributedstring) {
        //存储所有的匹配结果前,将原来的清空
        linkranges.removeall()
        //正则匹配范围--整个label
        let regexrange = nsrange(location: 0, 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: 0) , range: regexrange)
            for range in results {      //每一种正则法则可能匹配到多个符合要求的对象如@张三  @李四 匹配到两个,结果是个数组
                linkranges.append(range.rangeatindex(0))
            }
        }
    }

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

    //mark:4.绘制textstorage的文本内容--不能调用super
    override func drawtextinrect(rect: cgrect) {
        let range = nsmakerange(0, textstorage.length)
        //glyphs--字形---cgpoint()从原点绘制,也就是右上角
        layoutmanager.drawglyphsforglyphrange(range, atpoint: cgpoint(x: 0,y: 0))
    }

    //mark:5.用户点击事件交互--处理不同匹配内容天转到不同界面
    override func touchesbegan(touches: set, 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]? {
        //正则表达式--@好友
        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: 0,length: textstorage.length))
        //遍历数组
        var ranges = [nsrange]()
        for match in matchs {
            ranges.append(match.rangeatindex(0))
        }
        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: 0,length: textstorage.length))
        //遍历数组
        var ranges = [nsrange]()
        for match in matchs {
            ranges.append(match.rangeatindex(0))
        }
        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\\.\\-~!@#$%^&*+?:[a-za-z0-9_/=<>]]*)?)|(www.[a-za-z0-9\\.\\-]+\\.([a-za-z]{2,4})(:\\d+)?(/[a-za-z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)"
        guard let regx = try? nsregularexpression(pattern: pattern, options: []) else {
            return nil
        }
        //多重匹配--//let matchs: [nstextcheckingresult]
        let matchs = regx.matchesinstring(textstorage.string, options: [], range: nsrange(location: 0,length: textstorage.length))
        //遍历数组
        var ranges = [nsrange]()
        for match in matchs {
            ranges.append(match.rangeatindex(0))
        }
        return ranges
    }
}