iOS: 利用TextKit實現(xiàn)UILabel的高亮芽狗、可交互

TextKit的相關知識可以看這一篇文章:點我

要使UILabel用上TextKit绢掰,需要自定義UILabel

先看看使用這個自定義Label的demo

demo.gif

1. 自定義UILabel并創(chuàng)建TextKit三個核心對象

    /// NSAttributedString 子類 設置文本統(tǒng)一使用
    fileprivate lazy var textStorage = NSTextStorage()
    /// 布局管理器 負責 字形 布局
    fileprivate lazy var layoutManager = NSLayoutManager()
    /// 繪制區(qū)域
    fileprivate lazy var textContainer = NSTextContainer()

2.設置TextKit

    /// 準備文本系統(tǒng)
    fileprivate func prepareTextSystem() {
        
        adjustsFontSizeToFitWidth = true
        
        // 打開交互
        isUserInteractionEnabled = true
        
        // 準備文本內容
        prepareText()
        
        // 設置對象的關系
        textStorage.addLayoutManager(layoutManager)
        layoutManager.addTextContainer(textContainer)
        
    }
    
    /// 準備文本內容 - 使用TextStorage 接管 label內容
    fileprivate func prepareText() {
        if let attributedText = attributedText {
            textStorage.setAttributedString(attributedText)
        }else if let text = text {
            textStorage.setAttributedString(NSAttributedString(string: text))
        }else {
            textStorage.setAttributedString(NSAttributedString(string: ""))
            return
        }

  • 上一步需要在label初始化的時候執(zhí)行,所以需要兩個構造器里調用此方法
    // 代碼創(chuàng)建
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        prepareTextSystem()
    }
    
    // xib創(chuàng)建
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        prepareTextSystem()
    }
  • 指定文本繪制區(qū)域
  override func layoutSubviews() {
        super.layoutSubviews()
     
        textContainer.size = bounds.size
    }
  • 繪制textStorage的文本內容
override func drawText(in rect: CGRect) {
        
        // 繪制背景
        let range = NSRange(location: 0, length: textStorage.length)
        layoutManager.drawBackground(forGlyphRange: range, at: CGPoint(x: 0, y: 0))
        
        // 繪制字形
        layoutManager.drawGlyphs(forGlyphRange: range, at: CGPoint(x: -5, y: 0))
        
    }

注意:必須先繪制背景才能繪制字形!否則背景會覆蓋字形
到此滴劲,TextKit基本設置完了攻晒。

3. 利用正則尋找需要高亮的內容

例如微博需要高亮

  • 鏈接

  • @用戶

  • #話題#

  • 根據(jù)正則表達式在textStorage中尋找對應的range

    private func findRanges(pattern: String) -> [NSRange]? {
        guard let regx = try? NSRegularExpression(pattern: pattern, options: []) else {
            return nil
        }
        
        let matches = regx.matches(in: textStorage.string, options: [], range: NSRange(location: 0, length: textStorage.length))
        
        var ranges = [NSRange]()
        for match in matches {
            ranges.append(match.rangeAt(0))
        }
        
        return ranges
    }
  • 定義計算型屬性保存ranges
    /// 鏈接
    var urlRanges: [NSRange]? {
        let pattern = "\\b(([\\w-]+://?|www[.])[^\\s()<>]+(?:\\([\\w\\d]+\\)|([^[:punct:]\\s]|/)))"
        
        return findRanges(pattern: pattern)
    }
    
    /// 話題
    var topicRanges: [NSRange]? {
        let pattern = "#[^#]+#"
        
        return findRanges(pattern: pattern)
    }
    
    /// @用戶
    var atRanges: [NSRange]? {
        let pattern = "@[\\u4e00-\\u9fa5a-zA-Z0-9_-]{2,30}"
        
        return findRanges(pattern: pattern)
    }

4. 設置屬性文本

找到相應的range后就可以設置顏色、背景色等等屬性了

fileprivate func setupTextAttributes() {
        
        textStorage.addAttributes([NSFontAttributeName: font,
                                   NSForegroundColorAttributeName: textColor],
                                  range: NSRange(location: 0, length: textStorage.length))
        
        for range in urlRanges ?? [] {
            textStorage.addAttributes([NSForegroundColorAttributeName: UIColor.blue], range: range)
        }
        
        for range in topicRanges ?? [] {
            textStorage.addAttributes([NSForegroundColorAttributeName: UIColor.blue], range: range)
        }
        
        for range in atRanges ?? [] {
            textStorage.addAttributes([NSForegroundColorAttributeName: UIColor.blue], range: range)
        }
    }

5. 交互

  • 要使UILabel可交互前提是打開交互
isUserInteractionEnabled = true
  • 點擊高亮部分
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let location = touches.first?.location(in: self) else {
            return
        }
        
        // 獲取點擊了第幾個字符
        let index = layoutManager.glyphIndex(for: location, in: textContainer)
        
        // 判斷index是否在 range里
        for range in urlRanges ?? [] {
            if NSLocationInRange(index, range) {
                let str = (textStorage.string as NSString).substring(with: range)
                print("點擊了\(str)鏈接")
                return
            }
        }
        
        for range in topicRanges ?? [] {
            if NSLocationInRange(index, range) {
                let str = (textStorage.string as NSString).substring(with: range)
                print("點擊了\(str)話題")
                return
            }
        }
        
        for range in atRanges ?? [] {
            if NSLocationInRange(index, range) {
                let str = (textStorage.string as NSString).substring(with: range)
                print("點擊了\(str)用戶"))
                return
            }
        }
    }
  • 創(chuàng)建代理協(xié)議
    當點擊高亮文本后需要傳遞事件以及點擊的文本出去這時候可以利用代理協(xié)議哑芹。
protocol XXXLabelDelegate: NSObjectProtocol {
    func labelDidSelectedLink(text: String)
    func labelDidSelectedTopic(text: String)
    func labelDidSelectedAt(text: String)
}
weak var delegate: XXXLabelDelegate?

6. 重寫屬性

當在外部使用時炎辨,例如:

let label = XXXLabel()
label.frame = CGRect(x: 0, y: 0, width: 100, height: 30)
label.text = "@百度 https://www.baidu.com" // 或者 label.attributedText = “@百度 https://www.baidu.com”
label.font = UIFont.systemFont(ofSize: 15)

這時候label是沒有任何內容的,因為已經使用TextKit接管了UILabel聪姿,所以需要重寫屬性,給textStorage賦值然后再設置屬性文本即可

    override var text: String? {
        didSet {
            prepareText()
        }
    }
    
    override var attributedText: NSAttributedString? {
        didSet {
            prepareText()
        }
    }
    
    override var font: UIFont! {
        didSet {
            prepareText()
        }
    }
    
    override var textColor: UIColor! {
        didSet {
            prepareText()
        }
    }
    fileprivate func prepareText() {
        if let attributedText = attributedText {
            textStorage.setAttributedString(attributedText)
        }else if let text = text {
            textStorage.setAttributedString(NSAttributedString(string: text))
        }else {
            textStorage.setAttributedString(NSAttributedString(string: ""))
            return
        }
        
        // 設置Text屬性
        setupTextAttributes()
        
    }

  fileprivate func setupTextAttributes() {
    textStorage.addAttributes([NSFontAttributeName: font,
                               NSForegroundColorAttributeName: textColor],
                              range: NSRange(location: 0, length: textStorage.length))
    
    for range in urlRanges ?? [] {
        textStorage.addAttributes([NSForegroundColorAttributeName: UIColor.blue], range: range)
    }
    
    for range in topicRanges ?? [] {
        textStorage.addAttributes([NSForegroundColorAttributeName: UIColor.blue], range: range)
    }
    
    for range in atRanges ?? [] {
        textStorage.addAttributes([NSForegroundColorAttributeName: UIColor.blue], range: range)
    }
}

GitHub
覺得不錯的給個star碴萧,非常感謝。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末末购,一起剝皮案震驚了整個濱河市破喻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盟榴,老刑警劉巖曹质,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異擎场,居然都是意外死亡羽德,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門迅办,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宅静,“玉大人,你說我怎么就攤上這事站欺∫碳校” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵矾策,是天一觀的道長磷账。 經常有香客問我,道長贾虽,這世上最難降的妖魔是什么逃糟? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮榄鉴,結果婚禮上履磨,老公的妹妹穿的比我還像新娘。我一直安慰自己庆尘,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布巷送。 她就那樣靜靜地躺著驶忌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上付魔,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天聊品,我揣著相機與錄音,去河邊找鬼几苍。 笑死翻屈,一個胖子當著我的面吹牛,可吹牛的內容都是我干的妻坝。 我是一名探鬼主播伸眶,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼刽宪!你這毒婦竟也來了厘贼?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤圣拄,失蹤者是張志新(化名)和其女友劉穎嘴秸,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庇谆,經...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡岳掐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了饭耳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片串述。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哥攘,靈堂內的尸體忽然破棺而出剖煌,到底是詐尸還是另有隱情,我是刑警寧澤逝淹,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布耕姊,位于F島的核電站,受9級特大地震影響栅葡,放射性物質發(fā)生泄漏茉兰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一欣簇、第九天 我趴在偏房一處隱蔽的房頂上張望规脸。 院中可真熱鬧,春花似錦熊咽、人聲如沸莫鸭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽被因。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梨与,已是汗流浹背堕花。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留粥鞋,地道東北人缘挽。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像呻粹,于是被迫代替她去往敵國和親壕曼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內容