上面提到的任务有一个很好的solution。但是检查 unicode 标量的 Unicode.Scalar.Properties 对单个字符有好处。而且对字符串不够灵活。
我们可以改用正则表达式——更通用的方法。下面有它如何工作的详细描述。解决方案就到这里了。
解决方案
在 Swift 中,您可以使用具有此类计算属性的扩展来检查字符串是否是单个 Emoji 字符:
extension String {
var isSingleEmoji : Bool {
if self.count == 1 {
let emodjiGlyphPattern = "\\p{RI}{2}|(\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})|[\\p{Emoji}&&\\p{Other_symbol}])(\\x{200D}(\\p{Emoji}(\\p{EMod}|\\x{FE0F}\\x{20E3}?|[\\x{E0020}-\\x{E007E}]+\\x{E007F})|[\\p{Emoji}&&\\p{Other_symbol}]))*"
let fullRange = NSRange(location: 0, length: self.utf16.count)
if let regex = try? NSRegularExpression(pattern: emodjiGlyphPattern, options: .caseInsensitive) {
let regMatches = regex.matches(in: self, options: NSRegularExpression.MatchingOptions(), range: fullRange)
if regMatches.count > 0 {
// if any range found — it means, that that single character is emoji
return true
}
}
}
return false
}
}
工作原理(详细)
一个表情符号(一个字形)可以通过许多不同的符号、序列及其组合来复制。
Unicode specification 定义了几种可能的 Emoji 字符表示。
单字符表情符号
由单个 Unicode 标量复制的表情符号字符。
Unicode 将 Emoji Character 定义为:
emoji_character := \p{Emoji}
但这并不一定意味着这样的角色会被绘制为表情符号。一个普通的数字符号“1”的 Emoji 属性为真,尽管它仍可能被绘制为文本。并且有一个这样的符号列表:#、©、4 等。
人们应该认为,我们可以使用附加属性来检查:“Emoji_Presentation”。但它不是这样工作的。有一个像? 或 ? 这样的 Emoji,具有 Emoji_Presentation=false 属性。
为了确保字符默认绘制为表情符号,我们应该检查它的类别:它应该是“Other_symbol”。
所以,实际上单字符表情符号的正则表达式应该定义为:
emoji_character := \p{Emoji}&&\p{Other_symbol}
表情符号演示序列
一个字符,通常可以绘制为文本或表情符号。它的外观取决于一个特殊的跟随符号,一个表示选择器,它指示它的表示类型。 \x{FE0E} 定义文本表示。 \x{FE0F} 定义表情符号表示。
可以在 [此处](https://unicode.org/Public/emoji/12.1/emoji-variation-sequences.txt) 找到此类符号的列表。
Unicode 定义表示顺序如下:
emoji_presentation_sequence := emoji_character emoji_presentation_selector
它的正则表达式序列:
emoji_presentation_sequence := \p{Emoji} \x{FE0F}
表情键帽序列
该序列看起来与 Presentation 序列非常相似,但它在末尾多了一个标量:\x{20E3}。用于它的可能的基本标量的范围相当狭窄:0-9#* - 仅此而已。示例:1️⃣、8️⃣、*️⃣。
Unicode 定义键帽序列如下:
emoji_keycap_sequence := [0-9#*] \x{FE0F 20E3}
正则表达式:
emoji_keycap_sequence := \p{Emoji} \x{FE0F} \x{FE0F}
表情符号修饰符序列
某些表情符号可以修改外观,例如肤色。例如表情符号?可以不同:???????????。要定义一个 Emoji,在这种情况下称为“Emoji_Modifier_Base”,可以使用后续的“Emoji_Modifier”。
一般来说这样的顺序是这样的:
emoji_modifier_sequence := emoji_modifier_base emoji_modifier
要检测它,我们可以搜索正则表达式序列:
emoji_modifier_sequence := \p{Emoji} \p{EMod}
表情标志序列
标志是具有特定结构的表情符号。每个标志都用两个“Regional_Indicator”符号表示。
Unicode 将它们定义为:
emoji_flag_sequence := regional_indicator regional_indicator
例如,乌克兰的国旗??实际上用两个标量表示:\u{0001F1FA \u{0001F1E6}
正则表达式:
emoji_flag_sequence := \p{RI}{2}
表情符号标签序列 (ETS)
使用所谓的 tag_base 的序列,其后是由符号范围 \x{E0020}-\x{E007E} 组成的自定义标签规范,并以 tag_end 标记 \x{E007F} 结束。
Unicode 是这样定义的:
emoji_tag_sequence := tag_base tag_spec tag_end
tag_base := emoji_character
| emoji_modifier_sequence
| emoji_presentation_sequence
tag_spec := [\x{E0020}-\x{E007E}]+
tag_end := \x{E007F}
奇怪的是,Unicode 允许标签基于ED-14a 中的 emoji_modifier_sequence 或 emoji_presentation_sequence。但同时在同一 documentation 提供的正则表达式中,它们似乎仅基于单个 Emoji 字符检查序列。
在 Unicode 12.1 表情符号列表中,仅定义了 three such Emojis。它们都是英国国家的国旗:英格兰???????、苏格兰???????和威尔士???????。所有这些都基于一个表情符号字符。所以,我们最好只检查这样的序列。
正则表达式:
\p{Emoji} [\x{E0020}-\x{E007E}]+ \x{E007F}
表情符号零宽度连接序列(ZWJ 序列)
零宽度连接符是一个标量 \x{200D}。在它的帮助下,可以将几个本身已经是表情符号的字符组合成新的。
例如,“有父亲、儿子和女儿的家庭”表情符号???是由父亲?、女儿?和儿子?用ZWJ符号粘在一起的表情符号组合而成的。
允许将单个 Emoji 字符、Presentation 和 Modifier 序列等元素粘在一起。
此类序列的正则表达式通常如下所示:
emoji_zwj_sequence := emoji_zwj_element (\x{200d} emoji_zwj_element )+
所有的正则表达式
上面提到的所有表情符号都可以用一个正则表达式来描述:
\p{RI}{2}
| ( \p{Emoji}
( \p{EMod}
| \x{FE0F}\x{20E3}?
| [\x{E0020}-\x{E007E}]+\x{E007F}
)
|
[\p{Emoji}&&\p{Other_symbol}]
)
( \x{200D}
( \p{Emoji}
( \p{EMod}
| \x{FE0F}\x{20E3}?
| [\x{E0020}-\x{E007E}]+\x{E007F}
)
| [\p{Emoji}&&\p{Other_symbol}]
)
)*