iOS Emoji编解码(OBJ-C/Swift)

  1. 编码EMOJI表情字符串
    OBJ-C:
    扩展NSString
//编码EMOJI表情字符串
- (NSString *)encodeEmojiString {
    NSMutableString *attributeString = [[NSMutableString alloc] initWithString:self];
    NSString *regex_emoji = @"[\\ud83c\\udc00-\\ud83c\\udfff]|[\\ud83d\\udc00-\\ud83d\\udfff]|[\\ud83e\\udd00-\\ud83e\\udfff]|[\\u2600-\\u27ff]";
    
    NSError *error = nil;
    NSRegularExpression *re = [NSRegularExpression
                               regularExpressionWithPattern:regex_emoji
                               options:NSRegularExpressionCaseInsensitive
                               error:&error];
    if (!re) {
        DDLogInfo(@"[NSString toMessageString]: %@", [error localizedDescription]);
        return attributeString;
    }
    
    DDLogInfo(@"stringToUnicode:%@,%@",[NSString stringToUnicode:attributeString],attributeString);

    NSArray *resultArray = [re matchesInString:self options:0 range:NSMakeRange(0, self.length)];
    NSMutableArray *imageArray = [NSMutableArray arrayWithCapacity:resultArray.count];

    //根据匹配范围来用编码后的字符串进行相应的替换
    for(NSTextCheckingResult *match in resultArray) {
        //获取数组元素中得到range
        NSRange range = [match range];
        //获取原字符串中对应的值
        NSString *subStr = [self substringWithRange:range];
        //UTF8编码
        NSString *credentialName = [NSString emojiConvert:subStr];
        NSMutableDictionary *imageDic = [NSMutableDictionary dictionaryWithCapacity:2];
        if (credentialName) {
            [imageDic setObject:credentialName forKey:@"image"];
        }
        [imageDic setObject:[NSValue valueWithRange:range] forKey:@"range"];
        //把字典存入数组中
        [imageArray addObject:imageDic];
    }
    //从后往前替换,否则会引起位置问题
    for (int i = (int)imageArray.count -1; i >= 0; i--) {
        NSRange range = [imageArray[i][@"range"] rangeValue];
        //进行替换
        [attributeString replaceCharactersInRange:range withString:imageArray[i][@"image"]];
    }
    return attributeString;
}
//对emoji转码
- (NSString *)emojiConvert:(NSString *)obj {
    DDLogInfo(@"stringToUnicode:对emoji转码");
    NSString *charactersToEscape = @"?!@#$^&%*+,:;='\"`<>()[]{}/\\| ";
    //invertedSet反转字符集,仅包含当前字符集中不存在的字符
    NSCharacterSet *allowedCharacters = [[NSCharacterSet
                                          characterSetWithCharactersInString:charactersToEscape] invertedSet];
    NSString *encodeStr = [obj stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters];
    NSString *result = [NSString stringWithFormat:@"<<%@>>", encodeStr];
    return result;
}

Swift:

///编码EMOJI表情字符串
    func encodeEmojiString() -> String {
        let regex_emoji = "[\\ud83c\\udc00-\\ud83c\\udfff]|[\\ud83d\\udc00-\\ud83d\\udfff]|[\\ud83e\\udd00-\\ud83e\\udfff]|[\\u2600-\\u27ff]"
        var regularExpression:NSRegularExpression?
        do {
             try regularExpression = NSRegularExpression(pattern: regex_emoji, options: .caseInsensitive)
        } catch {
            return self
        }
        guard let regularExpression = regularExpression else {
            return self
        }
        
        let resultArray = regularExpression.matches(in: self, options: .reportProgress, range: NSMakeRange(0, self.count))
        
        var imageArray:[Dictionary<String, Any>] = []
        //根据匹配范围来用编码后的字符串进行相应的替换
        for match in resultArray {
            //获取数组元素中得到range
            guard let range = toRange(match.range) else { return self }
            //获取原字符串中对应的值
            let subStr = self[range]
            
            //对emoji转码
            let charactersToEscape = "?!@#$^&%*+,:;='\"`<>()[]{}/\\| "
            //invertedSet反转字符集,仅包含当前字符集中不存在的字符
            let allowedCharacters = CharacterSet.init(charactersIn: charactersToEscape).inverted
            let encodeStr = subStr.addingPercentEncoding(withAllowedCharacters: allowedCharacters)
            let credentialName = "<<\(encodeStr ?? "")>>"
            
            let imageDic = ["image":credentialName, "range":range] as [String : Any]
            imageArray.append(imageDic)
        }
        
        var resultStr = self
        //从后往前替换,否则会引起位置问题
        for dict in imageArray.reversed() {
            resultStr.replaceSubrange(dict["range"] as! Range<String.Index>, with: dict["image"] as! String)
        }
        return resultStr
    }
  1. 解码EMOJI表情字符串
    OBJ-C
    扩展NSString
//解码EMOJI表情字符串
- (NSString *)decodeEmojiString {
    NSMutableString *attributeString = [[NSMutableString alloc] initWithString:self];
    NSString *regex_emoji = @"\\<\\<(.*?)\\>\\>";
    
    NSError *error = nil;
    NSRegularExpression *re = [NSRegularExpression
                               regularExpressionWithPattern:regex_emoji
                               options:NSRegularExpressionCaseInsensitive
                               error:&error];
    if (!re) {
        DDLogInfo(@"[NSString toMessageString]: %@", [error localizedDescription]);
        return attributeString;
    }
    
    NSArray *resultArray = [re matchesInString:self options:0 range:NSMakeRange(0, self.length)];
    NSMutableArray *imageArray = [NSMutableArray arrayWithCapacity:resultArray.count];
    
    //根据匹配范围来用解码后的字符串进行相应的替换
    for(NSTextCheckingResult *match in resultArray) {
        //获取数组元素中得到range
        NSRange range = [match range];
        //获取原字符串中对应的值
        NSString *subStr = [self substringWithRange:range];
        //UTF8解码
        NSString *credentialName = [NSString emojiRecovery:subStr];

        NSMutableDictionary *imageDic = [NSMutableDictionary dictionaryWithCapacity:2];
        if (credentialName) {
            [imageDic setObject:credentialName forKey:@"image"];
        }
        [imageDic setObject:[NSValue valueWithRange:range] forKey:@"range"];
        //把字典存入数组中
        [imageArray addObject:imageDic];
    }
    //从后往前替换,否则会引起位置问题
    for (int i = (int)imageArray.count -1; i >= 0; i--) {
        NSRange range = [imageArray[i][@"range"] rangeValue];
        //进行替换
        [attributeString replaceCharactersInRange:range withString:imageArray[i][@"image"]];
    }
    return attributeString;
}

//对emoji解码
- (NSString *)emojiRecovery:(NSString *)obj {
    DDLogInfo(@"stringToUnicode:对emoji解码");
    //去除首尾指定字符串
    NSCharacterSet *characterSet= [NSCharacterSet characterSetWithCharactersInString:@"<>"];
    NSString *trimEndStr = [obj stringByTrimmingCharactersInSet:characterSet];
    NSString *decodeStr = trimEndStr.stringByRemovingPercentEncoding;
    if (StringNotEmpty(decodeStr)) {
        return decodeStr;
    } else {
        return obj;
    }
}

Swift:
也需要扩展NSString

extension NSString {
    ///解码EMOJI表情字符串
    func decodeEmojiString() -> NSString {
        let regex_emoji = "\\<\\<(.*?)\\>\\>"
        var regularExpression:NSRegularExpression?
        do {
             try regularExpression = NSRegularExpression(pattern: regex_emoji, options: .caseInsensitive)
        } catch {
            return self
        }
        guard let regularExpression = regularExpression else {
            return self
        }
        let resultArray = regularExpression.matches(in: (self as String), options: .reportProgress, range: NSMakeRange(0, self.length))
        var imageArray:[Dictionary<String, Any>] = []
        //根据匹配范围来用解码后的字符串进行相应的替换
        for match in resultArray {
            //获取数组元素中得到range
            let range = match.range
            //获取原字符串中对应的值
            let subStr:NSString = self.substring(with: range) as NSString
            //去除首尾指定字符串
            let characterSet = CharacterSet.init(charactersIn: "<>")
            let trimEndStr = subStr.trimmingCharacters(in: characterSet)
            let decodeStr = trimEndStr.removingPercentEncoding
            
            let imageDic = ["image":decodeStr ?? subStr, "range":range] as [String : Any]
            imageArray.append(imageDic)
        }
        
        let resultStr:NSMutableString = NSMutableString.init(string: self)
        //从后往前替换,否则会引起位置问题
        for dict in imageArray.reversed() {
            resultStr.replaceCharacters(in: dict["range"] as! NSRange, with: dict["image"] as! String)
        }
        return resultStr
    }
}

Swift中用到的扩展:

extension String {
     
    //Range转换为NSRange
    func toNSRange(_ range: Range<String.Index>) -> NSRange {
          guard let from = range.lowerBound.samePosition(in: utf16), let to = range.upperBound.samePosition(in: utf16) else {
              return NSMakeRange(0, 0)
          }
          return NSMakeRange(utf16.distance(from: utf16.startIndex, to: from), utf16.distance(from: from, to: to))
      }
     
    //NSRange转换为Range
    func toRange(_ range: NSRange) -> Range<String.Index>? {
        let indexStart = self.index(self.startIndex, offsetBy: range.location)
        let indexEnd = self.index(indexStart, offsetBy: range.length)
        return indexStart..<indexEnd
    }
}
  1. 总结
    坑:
    某些Emoji例如💁♀️,就是💁+♀,采用变型表单,为那些可以显示颜色和其他内容的显示器提供更多信息。

其中♀前后都有不可见字符,用来表示♀是需要和💁合并的。表示形式为:\u200d\ufe0f
而我们在编码Emoji时,将Emoji用<<>>括起来进行发送。
其中Swift语言编码的String,会将>这个符号和表情带的\u200d\ufe0f结合

>字符在String类型下的不同

上图就可以清楚的看出其中的不同。

转换成Unicode↓

字符 转Unicode
💁♀️ \ud83d\udc81\u200d\u2640\ufe0f
💁 \ud83d\udc81
单个♀ \u2640
用于表示Emoji更多信息的♀ \u200d\u2640\ufe0f
> \u0026\u0067\u0074\u003b
> \u0026\u0067\u0074\u003b\u200d
> \u0026\u0067\u0074\u003b\ufe0f

所以在实际使用时,使用NSString来代替String。

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

推荐阅读更多精彩内容

  • ![Flask](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAW...
    极客学院Wiki阅读 7,188评论 0 3
  • 不知不觉易趣客已经在路上走了快一年了,感觉也该让更多朋友认识知道易趣客,所以就谢了这篇简介,已做创业记事。 易趣客...
    Physher阅读 3,382评论 1 2
  • 双胎妊娠有家族遗传倾向,随母系遗传。有研究表明,如果孕妇本人是双胎之一,她生双胎的机率为1/58;若孕妇的父亲或母...
    邺水芙蓉hibiscus阅读 3,672评论 0 2
  • 晴天,拥抱阳光,拥抱你。雨天,想念雨滴,想念你。 我可以喜欢你吗可以啊 我还可以喜欢你吗可以,可是你要知道我们不可...
    露薇霜凝阅读 1,173评论 1 2