字符和字形的關(guān)系可能會有些混亂浇坐。我們將深入探討使用表情符號和Swift處理它們的方式摇予。假設(shè)您要檢查一個字符串是否包含一個或多個表情符號,你將如何處理吗跋?
背景
表情符號是電子消息和網(wǎng)頁中使用的表意文字和笑臉。表情符號存在各種類型宁昭,包括面部表情跌宛,常見對象,天氣的地點和類型以及動物积仗。
盡管表情符號在2010年在全球范圍內(nèi)受到歡迎疆拘,但自1997年以來已經(jīng)在日本使用。表情符號集最初由少于80個符號組成寂曹,現(xiàn)已增長到包含1200多個圖標(biāo)哎迄。
2010年也是將第一套Emoji添加到Unicode標(biāo)準(zhǔn)的一年。Unicode
是旨在統(tǒng)一處理和呈現(xiàn)文本的行業(yè)標(biāo)準(zhǔn)隆圆。它還包含來自世界各地的書寫系統(tǒng)的字符索引漱挚,包括當(dāng)前和古代的字符索引。該標(biāo)準(zhǔn)不斷增長渺氧,版本12.1包含近138,000個字符旨涝。
該標(biāo)準(zhǔn)不僅包括來自世界各地的字母表中的字符,而且還包括看不見且不能單獨使用的特殊字符侣背。我們稍后再討論白华。強烈建議您查看unicode-table.com,以了解其規(guī)模贩耐。只需向下滾動主頁上的表格即可發(fā)現(xiàn)各種組合和可能性弧腥。
這是Unicode標(biāo)準(zhǔn)中定義的一些字符示例
深入
Unicode
標(biāo)準(zhǔn)定義的每個字符都有一個十六進制標(biāo)識符(Unicode碼),并且字符被分為塊潮太,例如希伯來語或阿拉伯語管搪。
了解字符,字形和標(biāo)量之間的區(qū)別很重要消别。Unicode
由Unicode
數(shù)字指定的字符組成抛蚤。屏幕上可能不顯示字符。同樣寻狂,組合或字符可能會導(dǎo)致屏幕上出現(xiàn)一個字符岁经。Swift通過對術(shù)語進行細微的區(qū)分來區(qū)分它們。這是一個非常復(fù)雜的故事蛇券,但要點是:
- 字符串由字符組成
- 字符由unicode標(biāo)量組成
- 每個Unicode標(biāo)量代表一個Unicode字符
回到Unicode字符缀壤。下面是一個例子:笑臉(??)被識別為U + 1F600并且是表情段的一部分樊拓。*你可以通過幾種方式在Swift字符串中表示表情符號:
let smiley1 = "??"
let smiley2 = "\u{1F600}" // Hex code, also "??"
看到這里,有人說“因此塘慕,我們可以找到表情符號的unicode
段筋夏,并檢查字符是否來自該段?”
然而图呢,表情符號字符并不只有一個段条篷,運輸和地圖,補充符號和象形文字有單獨的段蛤织,其他符號和象形文字中有很多圖標(biāo)赴叹。
即使我們確定哪些段或哪些字符列表是emoji表情,也不是長久之計指蚜。該標(biāo)準(zhǔn)在不斷發(fā)展和擴展乞巧。
將此應(yīng)用于代碼
在Swift 4.2及之前的版本中,我們一直在嘗試通過檢查Unicode
數(shù)字是否屬于預(yù)定義的Unicode
段之一來確定字符是否為表情符號摊鸡。
extension String {
var containsEmoji: Bool {
for scalar in unicodeScalars {
switch scalar.value {
case 0x1F600...0x1F64F, // Emoticons
0x1F300...0x1F5FF, // Misc Symbols and Pictographs
0x1F680...0x1F6FF, // Transport and Map
0x2600...0x26FF, // Misc symbols
0x2700...0x27BF, // Dingbats
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs
0x1F1E6...0x1F1FF: // Flags
return true
default:
continue
}
}
return false
}
}
隨之而來的是Swift 5.0绽媒,它帶有一個新的Unicode.Scalar.Properties類,它為我們提供了一系列標(biāo)志免猾,以幫助我們弄清正在處理的內(nèi)容是辕。我們可以很容易地獲取表示我們字符串的Unicode
標(biāo)量數(shù)組。
下面舉個簡單的例子:
// emoji表情
let smiley = "??"
// 獲取字符串標(biāo)量
let scalars = smiley.unicodeScalars // UnicodeScalarView instance
// 我們只有一個字符掸刊,因此我們用first得到他
let firstScalar = scalars.first // is 128512
// 注意128512實際上是1F600(十六進制)的十進制(??的unicode標(biāo)識)
// 獲取屬性
let properties = firstScalar?.properties
// 檢查它是不是emoji表情
let isEmoji = properties?.isEmoji // = true
是不是這樣就可以了呢免糕?當(dāng)然不是,比如:
// 這里有個坑忧侧,這將會返回true
"3".unicodeScalars.first?.properties.isEmoji
這是因為標(biāo)量“ 3” 可以表示為表情符號石窑。屬性isEmoji
確實以這種方式引起誤解。幸運的是蚓炬,還有另一個屬性:
// 這將像以前一樣返回true:
"??" .unicodeScalars.first松逊?.properties.isEmojiPresentation
// 這將返回false,就像我們期望的那樣:
"3" .unicodeScalars.first肯夏?.properties.isEmojiPresentation
//不幸的是经宏,這并不適用于所有表情符號:
"??" .unicodeScalars.first?.properties.isEmojiPresentation //否
"??" .unicodeScalars.first驯击?.properties.generalCategory == .some(.otherSymbol)// true
我們還沒有真正的成功烁兰。實際上還有一些字符由多個字形組成』捕迹看看我們?nèi)绾问褂?code>unicodeScalars.first?
請考參考以下示例:
"1??".unicodeScalars.first?.properties.isEmojiPresentation //false
"??".unicodeScalars.first?.properties.isEmojiPresentation //false
"????".unicodeScalars.first?.properties.isEmojiPresentation //true
"????????".unicodeScalars.first?.properties.isEmojiPresentation // true
為了解釋為什么會發(fā)生這種情況沪斟,讓我們看一下unicodeScalars屬性。該屬性unicodeScalars
返回的實例UnicodeScalarView
暇矫。它的debugDescription
只會產(chǎn)生原始的String主之,因此直接檢查內(nèi)容(或記錄它)并不能提供太多的見解择吊。幸運的是,有一個map函數(shù)將返回一個常規(guī)數(shù)組槽奕,因此我們最終得到了一個元素數(shù)組:Unicode.Scalar
// 創(chuàng)建一個UnicodeScalarView
let scalarView = "1??".unicodeScalars
// 映射視圖几睛,以便我們得到一個常規(guī)數(shù)組,可以檢查
let scalars = scalarView.map { $0 }
結(jié)果包含三個值:
我們前面提到了那些特殊的標(biāo)量粤攒。因此所森,這些字符的組合用于形成表情符號,將常規(guī)數(shù)字1變成該符號夯接。第二和第三標(biāo)量修改了初始標(biāo)量必峰。
為了明確起見,您還可以使用十六進制unicode
標(biāo)識符手動創(chuàng)建此組合:
“ \ u {0031}” //變成:1
“ \ u {0031} \ u {20E3}” //變成:1?
“ \ u {0031} \ u {FE0F} \ u {20E3}” //變成:1??
同樣钻蹬,其他表情符號可以組合:
//黑色鉆石套裝表情符號
" \ u {2666}" //?
//添加'Variation Selector-16':
" \ u {2666} \ u {FE0F}" //??
//豎起大拇指標(biāo)志:
" \ u {1F44D}" //??
//添加'表情符號修飾符Fitzpatrick Type-4':
" \ u {1F44D} \ u {1F3FD}" //????
//男人,女人凭需,女孩问欠,男孩
" \ u {1F468} \ u {1F469} \ u {1F467} \ u {1F466}" //????????
// 在每個標(biāo)量之間添加空格對應(yīng)標(biāo)量,這是將7個標(biāo)量組合成一個字符粒蜈。
" \ u {1F468} \ u {200D} \ u {1F469} \ u ” {200D} \ u {1F467} \ u {200D} \ u {1F466}" // ????????
最后顺献,請注意,并非每個由多個標(biāo)量組成的字符都是一個表情符號:
"\∪{} 0061" //字母:a
"\∪{} 0302" //抑揚音: ^
"\∪{0061} \∪{} 0302" //組合成:①
小提示:也許您已經(jīng)看到在線的消息/文本看起來很混亂枯怖。這通常稱為Zalgo注整,實際上僅由許多Unicode字符組成,這些字符被合并為屏幕上的單個字符:
let lotsOfScalars =“ E??????????????”
let scalars = lotsOfScalars.unicodeScalars.map {$ 0}
// 合并為字符串度硝,并添加空格以單獨查看它們
// //結(jié)果為: E ? ? ? ? ? ? ?? ? ? ? ? ? ?
scalarList = scalars.reduce(""肿轨,{"\($ 0)\($ 1)"})
最終結(jié)論
讓我們結(jié)合這些信息,向Character
和String
類添加一些幫助屬性蕊程。我們會:
- 檢查一個字符是否恰好是將作為表情符號顯示的一個標(biāo)量
- 檢查一個字符是否由多個標(biāo)量組成椒袍,這些標(biāo)量將被組合成一個表情符號。
extension Character {
/// 簡單的emoji是一個標(biāo)量藻茂,以emoji的形式呈現(xiàn)給用戶
var isSimpleEmoji: Bool {
guardletfirstProperties = unicodeScalars.first?.propertieselse{
returnfalse
}
return unicodeScalars.count == 1 &&
(firstProperties.isEmojiPresentation||
firstProperties.generalCategory==.otherSymbol)
}
/// 檢查標(biāo)量是否將合并到emoji中
var isCombinedIntoEmoji: Bool {
return unicodeScalars.count > 1 &&
unicodeScalars.contains { $0.properties.isJoinControl || $0.properties.isVariationSelector }
}
/// 是否為emoji表情
/// - Note: http://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji
varisEmoji:Bool{
return isSimpleEmoji || isCombinedIntoEmoji
}
}
接下來驹暑,我們將一些計算的屬性添加到String
來訪問我們的Character
擴展:
extension String {
/// 是否為單個emoji表情
var isSingleEmoji: Bool {
return count == 1 && containsEmoji
}
/// 包含emoji表情
var containsEmoji: Bool {
return contains{ $0.isEmoji}
}
/// 只包含emoji表情
var containsOnlyEmoji: Bool {
return !isEmpty && !contains{!$0.isEmoji}
}
/// 提取emoji表情字符串
var emojiString: String {
returnemojis.map{String($0) }.reduce("",+)
}
/// 提取emoji表情數(shù)組
var emojis: [Character] {
returnfilter{ $0.isEmoji}
}
/// 提取單元編碼標(biāo)量
var emojiScalars: [UnicodeScalar] {
returnfilter{ $0.isEmoji}.flatMap{ $0.unicodeScalars}
}
}
現(xiàn)在檢查我們的字符串中的表情符號變得非常簡單:
"A???".containsEmoji // false
"3".containsEmoji // false
"A?????".unicodeScalars // [65, 795, 858, 790, 9654, 65039]
"A?????".emojiScalars // [9654, 65039]
"3??".isSingleEmoji // true
"3??".emojiScalars // [51, 65039, 8419]
"????".isSingleEmoji // true
"????♂?".isSingleEmoji // true
"????????".isSingleEmoji // true
"????????".containsOnlyEmoji // true
"Hello ????????".containsOnlyEmoji // false
"Hello ????????".containsEmoji // true
"?? Héllo ????????".emojiString // "??????????"
"????????".count // 1
"?? Héll? ????????".emojiScalars // [128107, 128104, 8205, 128105, 8205, 128103, 8205, 128103]
"?? Héll? ????????".emojis // ["??", "????????"]
"?? Héll? ????????".emojis.count // 2
"????????????????".isSingleEmoji // false
"????????????????".containsOnlyEmoji // true
總結(jié)一下
字符和標(biāo)量之間有一個重要的區(qū)別”娲停基本上优俘,先定義標(biāo)量的字符串,然后由系統(tǒng)渲染該字符串以確定標(biāo)量將顯示哪些字符掀序。
英文好的的童鞋可以看這里原文鏈接帆焕,雖然Unicode將每個代碼點定義為字符,但是Swift確實會調(diào)用這些標(biāo)量森枪,并將術(shù)語“字符”用于標(biāo)量的組合视搏,這可能會導(dǎo)致字符串中出現(xiàn)單個字形审孽。我覺得,因此諸如控制字符(即“ null ”和“ backspace ”)之類將被計為一個單獨的字符浑娜。