如何突出显示 Swift 中包含表情符号的字符串中的文本?

How do I highlight text in a string that contains emojis in Swift?

我有以下功能可以在 UILabel 中查找并突出显示主题标签或提及(@ 或 #):

class func addLinkAttribute(pattern: String,
        toText text: String,
        withAttributeName attributeName : String,
        toAttributedString attributedString :NSMutableAttributedString,
        withLinkAttributes linkAttributes: [NSObject : AnyObject]) {
        var error: NSError?
        if let regex = NSRegularExpression(pattern: pattern, options:.CaseInsensitive, error: &error) {
            regex.enumerateMatchesInString(text, options: .allZeros, range: NSMakeRange(0, count(text))) { result, flags, stop in
                let range = result.range
                let start = advance(text.startIndex, range.location)
                let end = advance(start, range.length)
                let foundText = text.substringWithRange(Range<String.Index>(start: start,end: end))
                var linkAttributesWithName = linkAttributes
                linkAttributesWithName[attributeName] = foundText
                attributedString.addAttributes(linkAttributesWithName, range: range)
            }
        }
    }

如果我传递主题标签 (#)(\w+) 或提及 (@)(\w+) 模式,代码将完美运行,但如果文本包含表情符号,则范围会被其前面的表情符号数量偏移:

我知道 Swift 对待字符串的方式与 Objective-C 不同,因为 count(string)count(string.utf16) 给我不同的结果,但我不知道如何解释这一点使用正则表达式时。

我可以只检查 2 个计数之间的差异并抵消范围,但这对我来说似乎是错误的和 hacky。肯定还有别的办法。

类似,一个可能的解决方案是将给定的 Swift StringNSString 并应用返回的 NSRanges enumerateMatchesInString() 到那个 NSString:

class func addLinkAttribute(pattern: String,
    toText text: String,
    withAttributeName attributeName : String,
    toAttributedString attributedString :NSMutableAttributedString,
    withLinkAttributes linkAttributes: [NSObject : AnyObject]) {
    let nsText = text as NSString
    var error: NSError?
    if let regex = NSRegularExpression(pattern: pattern, options:.CaseInsensitive, error: &error) {
        regex.enumerateMatchesInString(text, options: .allZeros, range: NSMakeRange(0, nsText.length)) {
            result, _, _ in
            let range = result.range
            let foundText = nsText.substringWithRange(range)
            var linkAttributesWithName = linkAttributes
            linkAttributesWithName[attributeName] = foundText
            attributedString.addAttributes(linkAttributesWithName, range: range)
        }
    }
}

(替代解决方案。) 可以将 NSRange 转换为 Range<String.Index>,而无需中间转换为 NSString。 随着

extension String {
    func rangeFromNSRange(nsRange : NSRange) -> Range<String.Index>? {
        let utf16start = self.utf16.startIndex
        if let from = String.Index(self.utf16.startIndex + nsRange.location, within: self),
            let to = String.Index(self.utf16.startIndex + nsRange.location + nsRange.length, within: self) {
                return from ..< to
        }
        return nil
    }
}

来自,你的代码可以是 写成

class func addLinkAttribute(pattern: String,
    toText text: String,
    withAttributeName attributeName : String,
    toAttributedString attributedString :NSMutableAttributedString,
    withLinkAttributes linkAttributes: [NSObject : AnyObject]) {

    var error: NSError?
    if let regex = NSRegularExpression(pattern: pattern, options:.CaseInsensitive, error: &error) {
        regex.enumerateMatchesInString(text, options: .allZeros, range: NSMakeRange(0, count(text.utf16))) {
            result, _, _ in
            let nsRange = result.range
            if let strRange = text.rangeFromNSRange(nsRange) {
                let foundText = text.substringWithRange(strRange)
                var linkAttributesWithName = linkAttributes
                linkAttributesWithName[attributeName] = foundText
                attributedString.addAttributes(linkAttributesWithName, range: nsRange)
            }
        }
    }
}

并且这也应该适用于所有类型的扩展字素 集群(表情符号、区域指标等...)