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

需求背景:
在及时通讯聊天中,一般可以嵌套在文本中的表情为特殊字符,在UI显示的时候,再通过富文本处理为相应的图片。


首先,微信里面可以识别的表情符为“[]”或者“/:*”。
第一步,我们得准备好相应的表情png。
然后,编写相应的字典,让图片和字符一一对应。
如下:

<?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>[发呆]</key>
    <string>Expression_4</string>
    <key>[发怒]</key>
    <string>Expression_12</string>
    <key>[发抖]</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>[太阳]</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>[强]</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>[爱你]</key>
    <string>Expression_88</string>
    <key>[爱心]</key>
    <string>Expression_67</string>
    <key>[爱情]</key>
    <string>Expression_91</string>
    <key>[猪头]</key>
    <string>Expression_63</string>
    <key>[玫瑰]</key>
    <string>Expression_64</string>
    <key>[瓢虫]</key>
    <string>Expression_74</string>
    <key>[疑问]</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>[调皮]</key>
    <string>Expression_13</string>
    <key>[足球]</key>
    <string>Expression_73</string>
    <key>[跳绳]</key>
    <string>Expression_99</string>
    <key>[跳跳]</key>
    <string>Expression_93</string>
    <key>[转圈]</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>[阴险]</key>
    <string>Expression_52</string>
    <key>[难过]</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>

通过plist文件先将表情符和对应图片名一一对应,这个方法,也可以用来做自定义表情。


第二步,就是拿到含有表情字符串后,对相对应的字符进行检测并一一替换,主要用到了以下几个类

NSMutableAttributedString
NSTextAttachment

主题思路如下:
首先利用正则表达式匹配“[]”内容,然后利用NSTextAttachment将表情替换上去。

按照这个思路去添加,先列出主要方法吧:

 /// 替换字符中的表情为图片的方法
    ///
    /// - Parameters:
    ///   - string: 需要替换的字符
    ///   - expression: 替换的规则类
    /// - 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 的值,从下一个表情的位置开始
            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类,是自定义的一个负责处理,表情plist文件,匹配的正则表达式,以及图片存放bundle的类。
SWTextAttachment主要是继承了NSTextAttachment类,负责管理图片,在里面自定义并ovverride了一些父类的方法,目的是为了适配字符本身的大小,以及避免图片失真的方法。
以上思路主要参考了,MLLabel的实现,传送门如下:
https://github.com/molon/MLLabel


进一步,是关于“/:XXX” 类型的表情字符,这类字符由于只有/:开头的特征,长度不定,所以无法用正则去匹配,我的解决策略如下:
首先,收集所有表示表情的字符:

<?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>[发呆]</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>[发怒]</string>
    <key>/::P</key>
    <string>[调皮]</string>
    <key>/::D</key>
    <string>[呲牙]</string>
    <key>/::O</key>
    <string>[惊讶]</string>
    <key>/::(</key>
    <string>[难过]</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>[疑问]</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>[爱心]</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>[太阳]</string>
    <key>/:gift</key>
    <string>[礼物]</string>
    <key>/:hug</key>
    <string>[拥抱]</string>
    <key>/:strong</key>
    <string>[强]</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>[爱你]</string>
    <key>/:no</key>
    <string>[NO]</string>
    <key>/:ok</key>
    <string>[OK]</string>
    <key>/:&lt;L&gt;</key>
    <string>[飞吻]</string>
    <key>/:love</key>
    <string>[爱情]</string>
    <key>/:jump</key>
    <string>[跳跳]</string>
    <key>/:shake</key>
    <string>[发抖]</string>
    <key>/:&lt;O&gt;</key>
    <string>[怄火]</string>
    <key>/:circle</key>
    <string>[转圈]</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>[激动]</string>
    <key>/:hiphot</key>
    <string>[乱舞]</string>
    <key>/:kiss</key>
    <string>[献舞]</string>
    <key>/:&lt;&amp;</key>
    <string>[左太极]</string>
    <key>/:&amp;&gt;</key>
    <string>[右太极]</string>
</dict>
</plist>

然后,遍历这个字典:

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

如果有,就替换,没有及继续遍历,直到字符串中没有"/:"为止。
这个算法的时间复杂度为O(n), n为字典的长度。
但个人认为实际应该可以优化。
经过替换以后,用[]的解决方案即可解决。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容