Swift替换含有emoji的富文本内容

1,174 阅读1分钟

公司有一个表情键盘的需求,在输入完表情发送时需要将表情转化为文字与服务端交互。 于是我写了一个UITextView的Category来进行转换

@objc extension UITextView {
    @objc public var plainText: String {
        var string = self.attributedText.string
        let originCount = string.count
        self.attributedText.enumerateAttribute(NSAttributedString.Key.attachment, in: NSRange(location: 0, length: self.attributedText.length), options: []) { (value, range, _) in
            if let attachment = value as? EmojiTextAttachment {
                let startIndex = string.index(string.endIndex, offsetBy: -(originCount - range.location))
                let endIndex = string.index(string.endIndex, offsetBy: -(originCount - range.location - 1))
                string.replaceSubrange(startIndex ..< endIndex, with: attachment.textInput)
            }
        }
        return string 
    }
}

后来测试说输入emoji的情况下会闪退,仔细观察了一遍代码,想到了在swift中使用string.count获得的字符数会比OC中少,例如“😁😁😁笑笑笑”这样一段字符,使用string.count的出来的是6,而NSAttributeString.length获得的却是8,这就导致在字符串末尾处的startIndex越界了。 由于之前遇到过类似的情况,知道string还提供了string.utf16.count来获得正确字符数,于是稍作修改,运行一遍发现又闪退了... 做了一点测试后发现,虽然originCount已经取得了正确字符数,但是string.endIndex仍然是使用的swift的计数方式,于是在字符串的开头处startIndex就越界了。 最后也没有想到什么很好的办法,只能通过NSString来进行文本替换,最终的代码如下:

@objc extension UITextView {
    @objc public var plainText: String {
        var string = NSString.init(string:self.attributedText.string)
        let originCount = string.length
        self.attributedText.enumerateAttribute(NSAttributedString.Key.attachment, in: NSRange(location: 0, length: self.attributedText.length), options: []) { (value, range, _) in
            if let attachment = value as? EmojiTextAttachment {
                let replaceRange = NSRange(location: string.length - ( originCount - range.location), length: 1)
                string = string.replacingCharacters(in: replaceRange, with: attachment.textInput) as NSString
            }
        }
        return string as String
    }
}

这样最终也是可以解决崩溃。 swift中关于emoji的处理可以看这篇文章:Swift 4 中的字符串