I use a UITextView with text attributes editing enabled and I am having issue with getting the attributes when there are emojis in the text.
Here is the code I use:
var textAttributes = [(attributes: [NSAttributedString.Key: Any], range: NSRange)]()
let range = NSRange(location: 0, length: textView.attributedText.length)
textView.attributedText.enumerateAttributes(in: range) { dict, range, _ in
textAttributes.append((attributes: dict, range: range))
}
for attribute in textAttributes {
if let swiftRange = Range(attribute.range, in: textView.text) {
print("NSRange \(attribute.range): \(textView.text![swiftRange])")
} else {
print("NSRange \(attribute.range): cannot convert to Swift range")
}
}
And when I try it with a text like "Sample text ❤️", here is the output:
NSRange {0, 12}: Sample text
NSRange {12, 1}: cannot convert to Swift range
NSRange {13, 1}: cannot convert to Swift range
So as you can see, I cannot get the text with the emoji in it.
The text attributes are set by my custom NSTextStorage applied on the text view. Here is the setAttributes method:
override func setAttributes(_ attrs: [NSAttributedString.Key: Any]?, range: NSRange) {
guard (range.location + range.length - 1) < string.count else {
print("Range out of bounds")
return
}
beginEditing()
storage.setAttributes(attrs, range: range)
edited(.editedAttributes, range: range, changeInLength: 0)
endEditing()
}
Note that during editing my text view, I have some "Range out of bounds" prints.
Is there a way to convert the NSRange to a valid Swift Range?
The most important thing to remember when working with NSAttributedString, NSRange, and String is that NSAttributedString (and NSString) and NSRange are based on UTF-16 encoded lengths. But String and its count are based on actual character counts. They don't mix.
If you ever try to create an NSRange with someSwiftString.count, you will get the wrong range. Always use someSwiftString.utf16.count.
In your specific case you are applying attributes to half for the ❤️ character due to the wrong length in the NSRange and that cascades to the errors you see.
And in the code you posted, you need to change:
guard (range.location + range.length - 1) < string.count else {
to:
guard (range.location + range.length - 1) < string.utf16.count else {
for the same reasons described above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With