需求背景:
在及时通讯聊天中,一般可以嵌套在文本中的表情为特殊字符,在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>/::<</key>
<string>[流泪]</string>
<key>/::$</key>
<string>[害羞]</string>
<key>/::X</key>
<string>[闭嘴]</string>
<key>/::Z</key>
<string>[睡]</string>
<key>/::'(</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>/::></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>/:&-(</key>
<string>[糗大了]</string>
<key>/:B-(</key>
<string>[坏笑]</string>
<key>/:<@</key>
<string>[左哼哼]</string>
<key>/:@></key>
<string>[右哼哼]</string>
<key>/::-O</key>
<string>[哈欠]</string>
<key>/:>-|</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>/:<W></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>/:<L></key>
<string>[飞吻]</string>
<key>/:love</key>
<string>[爱情]</string>
<key>/:jump</key>
<string>[跳跳]</string>
<key>/:shake</key>
<string>[发抖]</string>
<key>/:<O></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>/:<&</key>
<string>[左太极]</string>
<key>/:&></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为字典的长度。
但个人认为实际应该可以优化。
经过替换以后,用[]的解决方案即可解决。
网友评论