计数标志表情符号时,Swift countElements()返回不正确的值

let str1 = "🇩🇪🇩🇪🇩🇪🇩🇪🇩🇪" let str2 = "🇩🇪.🇩🇪.🇩🇪.🇩🇪.🇩🇪." println("\(countElements(str1)), \(countElements(str2))") 

结果:1,10

但不应该有5个元素?

该错误似乎只发生在我使用标志表情符号时。

Swift 4(Xcode 9)的更新

从Swift 4(用Xcode 9 betatesting)开始,每个第二个区域性指示符号都会按照Unicode 9标准的规定来破坏字形集群:

 let str1 = "🇩🇪🇩🇪🇩🇪🇩🇪🇩🇪" print(str1.count) // 5 print(Array(str1)) // ["🇩🇪", "🇩🇪", "🇩🇪", "🇩🇪", "🇩🇪"] 

String也是它的字符集合,所以可以用str1.count获得字符数。


(老斯威夫特3和更旧的答案:)

从“标准附录#29 UNICODE TEXT SEGMENTATION”中的“3字形组合边界 ”:(重点增加):

一个遗留字形群被定义为一个基(例如A或カ),后跟零个或多个连续字符。 考虑这一点的一种方式是作为形成“堆栈”的一系列字符。

基数可以是单个字符,也可以是由Unicode标准中的D133定义的形成韩文音节的任何韩语encryption字符序列 ,也可以是任何Regional_Indicator(RI)字符序列 。 RI字符成对用来表示ISO国家代码对应的表情符号国旗符号。 多于两个RI字符的序列应由其他字符分隔 ,如U + 200B ZWSP。

(感谢@ printaro的链接)。

Swift字符表示一个扩展的字形集群,所以根据这个引用,正确的是任何区域指示符号的序列都被计算为单个字符。

您可以用“零宽度非连接器”分隔“标志”:

 let str1 = "🇩🇪\u{200C}🇩🇪" print(str1.characters.count) // 2 

或插入一个零宽度空间:

 let str2 = "🇩🇪\u{200B}🇩🇪" print(str2.characters.count) // 3 

这也解决了可能的歧义,例如“🇫🇷🇺🇸🇸”是“🇫🇷🇺🇸”还是“🇫🇷🇺🇸”?

另请参见如何知道两个表情符号是否会显示为一个表情符号? 关于一个可能的方法来计算一个Swiftstring中的“组合字符”的数量,这将返回5为您的let str1 = "🇩🇪🇩🇪🇩🇪🇩🇪🇩🇪"

下面是我如何解决这个问题, Swift 3

 let str = "🇩🇪🇩🇪🇩🇪🇩🇪🇩🇪" //or whatever the string of emojis is let range = str.startIndex..<str.endIndex var length = 0 str.enumerateSubstrings(in: range, options: NSString.EnumerationOptions.byComposedCharacterSequences) { (substring, substringRange, enclosingRange, stop) -> () in length = length + 1 } print("Character Count: \(length)") 

这解决了字符数和表情符号的所有问题,是我find的最简单的方法。

正如文档中提到的那样:

另请注意,countElements返回的字符数并不总是与包含相同字符的NSString的length属性相同。 NSString的长度基于string的UTF-16表示中的16位代码单元的数量,而不是string中的Unicode扩展字形群集的数量 。 为了体现这一点,NSString的长度属性在Swift String值被访问时被称为utf16Count。

使用CountElements的是Unicode扩展字形集群的数量。 由于str1具有所有相同的表情符号,CountElements返回1.如果您想要string的实际长度,请尝试utf16Count。

println(“(str1.utf16Count),(str2.utf16Count)”)

编辑:

请注意,计算string的长度需要迭代所有字符,因此是O(N)操作。 原因是不同的字符要求可变的内存量。 虽然大多数常用的字符适合16位甚至8位,但是其他表情符号需要32位2,并且由于基本字符可以具有无限的组合标记,因此字块群所需的存储在理论上是无界的。

所以要计算实际长度:

 var length = 0 for char in str1 { length = length + 1 } println(length)