NSAttributedString和emojis:位置和长度问题

时间:2017-02-08 13:47:05

标签: swift unicode nsattributedstring emoji nsrange

我使用NSAttributedString为来自API的文本的某些部分着色(想想" @提及"如在Twitter上)。

API为我提供了文本和一组实体,这些实体表示应该着色的文本部分(或链接,标签等)。

但有时,由于表情符号,颜色会被抵消。

例如,使用以下文字:

  

" @ericd一些文字。 @ apero"

API给出:

  

[      {         "文本" :" ericd",         " LEN" :6,         " POS" :0      },      {         "文本" :" apero",         " LEN" :6,         " POS" :18      }   ]

我使用NSRange成功转换为NSAttributedString:

for m in entities.mentions {
    let r = NSMakeRange(m.pos, m.len)
    myAttributedString.addAttribute(NSForegroundColorAttributeName, value: someValue, range: r)
}

我们看到"pos": 18是正确的,这就是" @ apero"开始。有色部分是" @ ericd"和" @ apero",正如所料。

但是当文本中使用表情符号的某些特定组合时,API无法很好地转换为NSATtributedString,颜色偏移

  

" @ericd一些文字。 ✌@ apero"

给出:

  

[      {         "文本" :" ericd",         " LEN" :6,         " POS" :0      },      {         "文本" :" apero",         " LEN" :6,         " POS" :22      }   ]

"pos": 22:API作者声明这是正确的,我理解他们的观点。

不幸的是,NSAttributedString不同意,我的颜色已关闭:

enter image description here

第二次提及的最后一个字符没有着色(因为" pos"由于表情符号而太短了?)。

正如您可能已经猜到的那样,我无论如何都无法改变API的行为方式,我必须在客户端进行调整。

除此之外......我不知道该怎么做。我是否应该尝试检测文本中的表情符号,并在有问题的表情符号时手动修改提及的位置?但是,检测哪个表情符号改变位置以及哪个表情符号没有变化的标准是什么?以及如何确定我需要多少偏移?也许问题是由NSAttributedString引起的?

据我所知,这与表情符号的长度相比,与他们作为离散字符的长度相比,但是......嗯......我迷失了(叹气)。

请注意,我已尝试实施类似于this stuff的解决方案,因为我的API与此版本兼容,但它只是部分工作,一些表情符号仍在破坏索引:

enter image description here

1 个答案:

答案 0 :(得分:18)

Swift String提供不同的视图"关于它的内容。 Swift Blog中的"Strings in Swift 2"给出了一个很好的概述:

  • characters是字符值或扩展字形集群的集合。
  • unicodeScalars是Unicode标量值的集合。
  • utf8是UTF-8代码单元的集合。
  • utf16是UTF-16代码单元的集合。

正如讨论中所述,来自您的API的poslen 是Unicode标量视图的索引。

另一方面,addAttribute()的{​​{1}}方法需要NSMutableAttributedString,即相应的范围 到NSRange中的UTF-16代码点的索引。

NSString提供了方法来翻译&#34;在指数之间 不同的观点(比较NSRange to Range<String.Index>):

String

let text = "@ericd Some text. ✌ @apero" let pos = 22 let len = 6 // Compute String.UnicodeScalarView indices for first and last position: let from32 = text.unicodeScalars.index(text.unicodeScalars.startIndex, offsetBy: pos) let to32 = text.unicodeScalars.index(from32, offsetBy: len) // Convert to String.UTF16View indices: let from16 = from32.samePosition(in: text.utf16) let to16 = to32.samePosition(in: text.utf16) // Convert to NSRange by computing the integer distances: let nsRange = NSRange(location: text.utf16.distance(from: text.utf16.startIndex, to: from16), length: text.utf16.distance(from: from16, to: to16)) 是您对属性字符串所需的内容:

NSRange

更新Swift 4(Xcode 9):在Swift 4中,标准库 提供了在Swift let attrString = NSMutableAttributedString(string: text) attrString.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: nsRange) 范围和String之间进行转换的方法 范围,因此计算简化为

NSString