天天看點

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
    }
}