用Swift處理微信、qq的表情及Emoji

需求背景:
在及時(shí)通訊聊天中,一般可以嵌套在文本中的表情為特殊字符,在UI顯示的時(shí)候,再通過(guò)富文本處理為相應(yīng)的圖片。


首先,微信里面可以識(shí)別的表情符為“[]”或者“/:*”。
第一步,我們得準(zhǔn)備好相應(yīng)的表情png。
然后,編寫相應(yīng)的字典,讓圖片和字符一一對(duì)應(yīng)。
如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>[NO]</key>
    <string>Expression_89</string>
    <key>[OK]</key>
    <string>Expression_90</string>
    <key>[乒乓]</key>
    <string>Expression_60</string>
    <key>[親親]</key>
    <string>Expression_53</string>
    <key>[便便]</key>
    <string>Expression_75</string>
    <key>[偷笑]</key>
    <string>Expression_21</string>
    <key>[傲慢]</key>
    <string>Expression_24</string>
    <key>[再見]</key>
    <string>Expression_40</string>
    <key>[冷汗]</key>
    <string>Expression_18</string>
    <key>[凋謝]</key>
    <string>Expression_65</string>
    <key>[刀]</key>
    <string>Expression_72</string>
    <key>[勾引]</key>
    <string>Expression_85</string>
    <key>[發(fā)呆]</key>
    <string>Expression_4</string>
    <key>[發(fā)怒]</key>
    <string>Expression_12</string>
    <key>[發(fā)抖]</key>
    <string>Expression_94</string>
    <key>[可憐]</key>
    <string>Expression_55</string>
    <key>[右哼哼]</key>
    <string>Expression_47</string>
    <key>[吐]</key>
    <string>Expression_20</string>
    <key>[嚇]</key>
    <string>Expression_54</string>
    <key>[呲牙]</key>
    <string>Expression_14</string>
    <key>[咒罵]</key>
    <string>Expression_32</string>
    <key>[咖啡]</key>
    <string>Expression_61</string>
    <key>[哈欠]</key>
    <string>Expression_48</string>
    <key>[啤酒]</key>
    <string>Expression_58</string>
    <key>[噓]</key>
    <string>Expression_34</string>
    <key>[嘴唇]</key>
    <string>Expression_66</string>
    <key>[回頭]</key>
    <string>Expression_98</string>
    <key>[困]</key>
    <string>Expression_26</string>
    <key>[壞笑]</key>
    <string>Expression_45</string>
    <key>[大哭]</key>
    <string>Expression_10</string>
    <key>[太陽(yáng)]</key>
    <string>Expression_77</string>
    <key>[奮斗]</key>
    <string>Expression_31</string>
    <key>[委屈]</key>
    <string>Expression_50</string>
    <key>[害羞]</key>
    <string>Expression_7</string>
    <key>[尷尬]</key>
    <string>Expression_11</string>
    <key>[左哼哼]</key>
    <string>Expression_46</string>
    <key>[差勁]</key>
    <string>Expression_87</string>
    <key>[弱]</key>
    <string>Expression_81</string>
    <key>[強(qiáng)]</key>
    <string>Expression_80</string>
    <key>[得意]</key>
    <string>Expression_5</string>
    <key>[微笑]</key>
    <string>Expression_1</string>
    <key>[心碎]</key>
    <string>Expression_68</string>
    <key>[快哭了]</key>
    <string>Expression_51</string>
    <key>[慪火]</key>
    <string>Expression_95</string>
    <key>[悠閑]</key>
    <string>Expression_30</string>
    <key>[驚恐]</key>
    <string>Expression_27</string>
    <key>[驚訝]</key>
    <string>Expression_15</string>
    <key>[愉快]</key>
    <string>Expression_22</string>
    <key>[憨笑]</key>
    <string>Expression_29</string>
    <key>[抓狂]</key>
    <string>Expression_19</string>
    <key>[投降]</key>
    <string>Expression_100</string>
    <key>[摳鼻]</key>
    <string>Expression_42</string>
    <key>[抱拳]</key>
    <string>Expression_84</string>
    <key>[擁抱]</key>
    <string>Expression_79</string>
    <key>[拳頭]</key>
    <string>Expression_86</string>
    <key>[握手]</key>
    <string>Expression_82</string>
    <key>[撇嘴]</key>
    <string>Expression_2</string>
    <key>[擦汗]</key>
    <string>Expression_41</string>
    <key>[敲打]</key>
    <string>Expression_39</string>
    <key>[暈]</key>
    <string>Expression_35</string>
    <key>[月亮]</key>
    <string>Expression_76</string>
    <key>[流汗]</key>
    <string>Expression_28</string>
    <key>[流淚]</key>
    <string>Expression_6</string>
    <key>[炸彈]</key>
    <string>Expression_71</string>
    <key>[愛(ài)你]</key>
    <string>Expression_88</string>
    <key>[愛(ài)心]</key>
    <string>Expression_67</string>
    <key>[愛(ài)情]</key>
    <string>Expression_91</string>
    <key>[豬頭]</key>
    <string>Expression_63</string>
    <key>[玫瑰]</key>
    <string>Expression_64</string>
    <key>[瓢蟲]</key>
    <string>Expression_74</string>
    <key>[疑問(wèn)]</key>
    <string>Expression_33</string>
    <key>[瘋了]</key>
    <string>Expression_36</string>
    <key>[白眼]</key>
    <string>Expression_23</string>
    <key>[睡]</key>
    <string>Expression_9</string>
    <key>[磕頭]</key>
    <string>Expression_97</string>
    <key>[禮物]</key>
    <string>Expression_78</string>
    <key>[籃球]</key>
    <string>Expression_59</string>
    <key>[糗大了]</key>
    <string>Expression_44</string>
    <key>[勝利]</key>
    <string>Expression_83</string>
    <key>[色]</key>
    <string>Expression_3</string>
    <key>[菜刀]</key>
    <string>Expression_56</string>
    <key>[蛋糕]</key>
    <string>Expression_69</string>
    <key>[衰]</key>
    <string>Expression_37</string>
    <key>[西瓜]</key>
    <string>Expression_57</string>
    <key>[調(diào)皮]</key>
    <string>Expression_13</string>
    <key>[足球]</key>
    <string>Expression_73</string>
    <key>[跳繩]</key>
    <string>Expression_99</string>
    <key>[跳跳]</key>
    <string>Expression_93</string>
    <key>[轉(zhuǎn)圈]</key>
    <string>Expression_96</string>
    <key>[鄙視]</key>
    <string>Expression_49</string>
    <key>[酷]</key>
    <string>Expression_17</string>
    <key>[閃電]</key>
    <string>Expression_70</string>
    <key>[閉嘴]</key>
    <string>Expression_8</string>
    <key>[陰險(xiǎn)]</key>
    <string>Expression_52</string>
    <key>[難過(guò)]</key>
    <string>Expression_16</string>
    <key>[飛吻]</key>
    <string>Expression_92</string>
    <key>[饑餓]</key>
    <string>Expression_25</string>
    <key>[飯]</key>
    <string>Expression_62</string>
    <key>[骷髏]</key>
    <string>Expression_38</string>
    <key>[鼓掌]</key>
    <string>Expression_43</string>
</dict>
</plist>

通過(guò)plist文件先將表情符和對(duì)應(yīng)圖片名一一對(duì)應(yīng),這個(gè)方法,也可以用來(lái)做自定義表情。


第二步,就是拿到含有表情字符串后,對(duì)相對(duì)應(yīng)的字符進(jìn)行檢測(cè)并一一替換,主要用到了以下幾個(gè)類

NSMutableAttributedString
NSTextAttachment

主題思路如下:
首先利用正則表達(dá)式匹配“[]”內(nèi)容,然后利用NSTextAttachment將表情替換上去。

按照這個(gè)思路去添加,先列出主要方法吧:

 /// 替換字符中的表情為圖片的方法
    ///
    /// - Parameters:
    ///   - string: 需要替換的字符
    ///   - expression: 替換的規(guī)則類
    /// - Returns: 返回替換后的富文本
    public class func expressionAttributedString(string: NSAttributedString, expression: SWExpression) -> NSAttributedString {
        let target: NSMutableAttributedString = string.mutableCopy() as! NSMutableAttributedString
        if target.length <= 0 {
            return target
        }
        let tempAttribute = NSMutableAttributedString.init()
        // 處理表情
        let resArr = expression.expressionRegularExpression.matches(in: target.string, options: .withTransparentBounds, range: NSRange.init(location: 0, length: target.length))
        var location: Int = 0;
        for resultText: NSTextCheckingResult in resArr {
            let range: NSRange = resultText.range
            let subString: NSAttributedString = target.attributedSubstring(from: NSRange.init(location: location, length: range.location - location))
            // 先把非表情部分加上去
            tempAttribute.append(subString)
            // 修改location 的值,從下一個(gè)表情的位置開始
            location = NSMaxRange(range)
            
            let expressionStr = target.attributedSubstring(from: range)
            let imageName: String = expression.expressionMap.object(forKey: expressionStr.string) as? String ?? ""
            // 如果圖片名存在
            if imageName.count > 0 {
                let bundle = Bundle.init(url: Bundle.main.url(forResource: expression.bundleName, withExtension: ".bundle")!)
                let image: UIImage = UIImage.init(named: imageName, in: bundle!, compatibleWith: nil)!
                let textAttachment = SWTextAttachment.init(lineHeightMultiple: 1.00, imageAspectRatio: image.size.width/image.size.height) { (imageBounds, textContainer, charIndex, textAttachment) -> UIImage in
                    return image
                }
                let attachmentString: NSMutableAttributedString = NSAttributedString.init(attachment: textAttachment).mutableCopy() as! NSMutableAttributedString
                expressionStr.enumerateAttributes(in: NSRange.init(location: 0, length: expressionStr.length), options: .longestEffectiveRangeNotRequired) { (attrs, range, stop) in
                    if attrs.count>0 && range.length==expressionStr.length {
                        attachmentString.addAttributes(attrs, range: NSRange.init(location: 0, length: attachmentString.length))
                    }
                }
                tempAttribute.append(attachmentString)
            } else {
                tempAttribute.append(expressionStr)
            }
        }
        if location < target.length {
            let range = NSRange.init(location: location, length: target.length - location)
            let sub = target.attributedSubstring(from: range)
            tempAttribute.append(sub)
        }
        return tempAttribute
    }

其中expression類,是自定義的一個(gè)負(fù)責(zé)處理,表情plist文件,匹配的正則表達(dá)式,以及圖片存放bundle的類。
SWTextAttachment主要是繼承了NSTextAttachment類,負(fù)責(zé)管理圖片,在里面自定義并ovverride了一些父類的方法,目的是為了適配字符本身的大小,以及避免圖片失真的方法。
以上思路主要參考了,MLLabel的實(shí)現(xiàn),傳送門如下:
https://github.com/molon/MLLabel


進(jìn)一步,是關(guān)于“/:XXX” 類型的表情字符,這類字符由于只有/:開頭的特征,長(zhǎng)度不定,所以無(wú)法用正則去匹配,我的解決策略如下:
首先,收集所有表示表情的字符:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>/::)</key>
    <string>[微笑]</string>
    <key>/::~</key>
    <string>[撇嘴]</string>
    <key>/::B</key>
    <string>[色]</string>
    <key>/::|</key>
    <string>[發(fā)呆]</string>
    <key>/:8-)</key>
    <string>[得意]</string>
    <key>/::&lt;</key>
    <string>[流淚]</string>
    <key>/::$</key>
    <string>[害羞]</string>
    <key>/::X</key>
    <string>[閉嘴]</string>
    <key>/::Z</key>
    <string>[睡]</string>
    <key>/::&apos;(</key>
    <string>[大哭]</string>
    <key>/::-|</key>
    <string>[尷尬]</string>
    <key>/::@</key>
    <string>[發(fā)怒]</string>
    <key>/::P</key>
    <string>[調(diào)皮]</string>
    <key>/::D</key>
    <string>[呲牙]</string>
    <key>/::O</key>
    <string>[驚訝]</string>
    <key>/::(</key>
    <string>[難過(guò)]</string>
    <key>/::+</key>
    <string>[酷]</string>
    <key>/::Q</key>
    <string>[抓狂]</string>
    <key>/::T</key>
    <string>[吐]</string>
    <key>/:,@P</key>
    <string>[偷笑]</string>
    <key>/:,@-D</key>
    <string>[愉快]</string>
    <key>/::d</key>
    <string>[白眼]</string>
    <key>/:,@o</key>
    <string>[傲慢]</string>
    <key>/::g</key>
    <string>[饑餓]</string>
    <key>/:|-)</key>
    <string>[困]</string>
    <key>/::!</key>
    <string>[驚恐]</string>
    <key>/::L</key>
    <string>[流汗]</string>
    <key>/::&gt;</key>
    <string>[憨笑]</string>
    <key>/::,@</key>
    <string>[悠閑]</string>
    <key>/:,@f</key>
    <string>[奮斗]</string>
    <key>/::-S</key>
    <string>[咒罵]</string>
    <key>/:?</key>
    <string>[疑問(wèn)]</string>
    <key>/:,@x</key>
    <string>[噓]</string>
    <key>/:,@@</key>
    <string>[暈]</string>
    <key>/::8</key>
    <string>[瘋了]</string>
    <key>/:,@!</key>
    <string>[衰]</string>
    <key>/:!!!</key>
    <string>[骷髏]</string>
    <key>/:xx</key>
    <string>[敲打]</string>
    <key>/:bye</key>
    <string>[再見]</string>
    <key>/:wipe</key>
    <string>[擦汗]</string>
    <key>/:dig</key>
    <string>[摳鼻]</string>
    <key>/:handclap</key>
    <string>[鼓掌]</string>
    <key>/:&amp;-(</key>
    <string>[糗大了]</string>
    <key>/:B-(</key>
    <string>[壞笑]</string>
    <key>/:&lt;@</key>
    <string>[左哼哼]</string>
    <key>/:@&gt;</key>
    <string>[右哼哼]</string>
    <key>/::-O</key>
    <string>[哈欠]</string>
    <key>/:&gt;-|</key>
    <string>[鄙視]</string>
    <key>/:P-(</key>
    <string>[委屈]</string>
    <key>/::*</key>
    <string>[親親]</string>
    <key>/:@x</key>
    <string>[嚇]</string>
    <key>/:8*</key>
    <string>[可憐]</string>
    <key>/:pd</key>
    <string>[菜刀]</string>
    <key>/:&lt;W&gt;</key>
    <string>[西瓜]</string>
    <key>/:beer</key>
    <string>[啤酒]</string>
    <key>/:basketb</key>
    <string>[籃球]</string>
    <key>/:oo</key>
    <string>[乒乓]</string>
    <key>/:coffee</key>
    <string>[咖啡]</string>
    <key>/:eat</key>
    <string>[飯]</string>
    <key>/:pig</key>
    <string>[豬頭]</string>
    <key>/:rose</key>
    <string>[玫瑰]</string>
    <key>/:fade</key>
    <string>[凋謝]</string>
    <key>/:showlove</key>
    <string>[嘴唇]</string>
    <key>/:heart</key>
    <string>[愛(ài)心]</string>
    <key>/:break</key>
    <string>[心碎]</string>
    <key>/:cake</key>
    <string>[蛋糕]</string>
    <key>/:li</key>
    <string>[閃電]</string>
    <key>/:bome</key>
    <string>[炸彈]</string>
    <key>/:kn</key>
    <string>[刀子]</string>
    <key>/:footb</key>
    <string>[足球]</string>
    <key>/:ladybug</key>
    <string>[瓢蟲]</string>
    <key>/:shit</key>
    <string>[便便]</string>
    <key>/:moon</key>
    <string>[月亮]</string>
    <key>/:sun</key>
    <string>[太陽(yáng)]</string>
    <key>/:gift</key>
    <string>[禮物]</string>
    <key>/:hug</key>
    <string>[擁抱]</string>
    <key>/:strong</key>
    <string>[強(qiáng)]</string>
    <key>/:weak</key>
    <string>[弱]</string>
    <key>/:share</key>
    <string>[握手]</string>
    <key>/:v</key>
    <string>[勝利]</string>
    <key>/:@)</key>
    <string>[抱拳]</string>
    <key>/:jj</key>
    <string>[勾引]</string>
    <key>/:@@</key>
    <string>[拳頭]</string>
    <key>/:bad</key>
    <string>[差勁]</string>
    <key>/:lvu</key>
    <string>[愛(ài)你]</string>
    <key>/:no</key>
    <string>[NO]</string>
    <key>/:ok</key>
    <string>[OK]</string>
    <key>/:&lt;L&gt;</key>
    <string>[飛吻]</string>
    <key>/:love</key>
    <string>[愛(ài)情]</string>
    <key>/:jump</key>
    <string>[跳跳]</string>
    <key>/:shake</key>
    <string>[發(fā)抖]</string>
    <key>/:&lt;O&gt;</key>
    <string>[慪火]</string>
    <key>/:circle</key>
    <string>[轉(zhuǎn)圈]</string>
    <key>/:kotow</key>
    <string>[磕頭]</string>
    <key>/:turn</key>
    <string>[回頭]</string>
    <key>/:skip</key>
    <string>[跳繩]</string>
    <key>/:oY</key>
    <string>[投降]</string>
    <key>/:#-0</key>
    <string>[激動(dòng)]</string>
    <key>/:hiphot</key>
    <string>[亂舞]</string>
    <key>/:kiss</key>
    <string>[獻(xiàn)舞]</string>
    <key>/:&lt;&amp;</key>
    <string>[左太極]</string>
    <key>/:&amp;&gt;</key>
    <string>[右太極]</string>
</dict>
</plist>

然后,遍歷這個(gè)字典:

extension String {
    mutating func emojiParse(emojiDic: Dictionary<String, String>) -> Void {
        for (key, value) in emojiDic {
            if self.contains(key) {
               self = self.replacingOccurrences(of: key, with: value)
            }
            if !self.contains("/:") {
                break
            }
        }
    }
}

如果有,就替換,沒(méi)有及繼續(xù)遍歷,直到字符串中沒(méi)有"/:"為止。
這個(gè)算法的時(shí)間復(fù)雜度為O(n), n為字典的長(zhǎng)度。
但個(gè)人認(rèn)為實(shí)際應(yīng)該可以優(yōu)化。
經(jīng)過(guò)替換以后,用[]的解決方案即可解決。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、通過(guò)CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明AI閱讀 16,204評(píng)論 3 119
  • 1. JS 模塊調(diào)用原生模塊方法 1.1 ReactContextBaseJavaModule 創(chuàng)建一個(gè)原生模塊 ...
    慌不要慌閱讀 3,532評(píng)論 4 11
  • 這一年 從開始忙到終結(jié) 時(shí)間的腳步 沒(méi)有在我身上停留 歲月的痕跡 留下了兩個(gè)暈圈在眼睛的周邊 和一抹亮麗的口紅 這...
    愛(ài)吃魚的大魚閱讀 309評(píng)論 0 2
  • 今天我們期待已久的畢業(yè)典禮已經(jīng)圓滿結(jié)束了。6點(diǎn)我和孩子一起去幼兒園參加了畢業(yè)典禮,開始的時(shí)候我們家長(zhǎng)和孩子們互動(dòng),...
    2021級(jí)張?chǎng)螡蓩寢?/span>閱讀 268評(píng)論 0 0

友情鏈接更多精彩內(nèi)容