Reputation: 28162
I'm helping build a keyboardextension and I've recently run into an issue with Swift 4 and emojis. The new UTF-16 emoji support for Swift 4 is really nice but there is an issue with adjustTextPosition
in UIInputViewController
.
If we call adjustTextPosition
to step over an emoji it will simply not step far enough, it seems like the characteroffset used by UIInputViewController
doesn't match the character count used by the system.
To test simply write a text with emojis and whenever some key is clicked call:
super.textDocumentProxy.adjustTextPosition(byCharacterOffset: 1)
What can be observed is that we have to click it more than what is to be expected.
Upvotes: 2
Views: 515
Reputation: 482
try this
let correctedOffset = adjust(offset: offset)
textDocumentProxy.adjustTextPosition(byCharacterOffset: correctedOffset)
private func adjust(offset: Int) -> Int {
if offset > 0, let after = textDocumentProxy.documentContextAfterInput {
let offsetStringIndex = after.index(after.startIndex, offsetBy: offset)
let chunk = after[..<offsetStringIndex]
let characterCount = chunk.utf16.count
return characterCount
} else if offset < 0, let before = textDocumentProxy.documentContextBeforeInput {
let offsetStringIndex = before.index(before.endIndex, offsetBy: offset)
let chunk = before[offsetStringIndex...]
let characterCount = chunk.utf16.count
return -1*characterCount
} else {
return offset
}
}
Upvotes: 0
Reputation: 2555
Adjusting caret position measured in grapheme clusters (Swift Characters):
func adjustCaretPosition(offset: Int) {
guard let textAfterCaret = textDocumentProxy.documentContextAfterInput else { return }
if let offsetIndex = offset > 0 ? textAfterCaret.index(textAfterCaret.startIndex, offsetBy: offset, limitedBy: textAfterCaret.endIndex) : textBeforeCaret.index(textBeforeCaret.endIndex, offsetBy: offset, limitedBy: textAfterCaret.startIndex),
let offsetIndex_utf16 = offsetIndex.samePosition(in: offset > 0 ? textAfterCaret.utf16 : textBeforeCaret.utf16)
{
let offset = offset > 0 ? textAfterCaret.utf16.distance(from: textAfterCaret.utf16.startIndex, to: offsetIndex_utf16) : textBeforeCaret.utf16.distance(from: textBeforeCaret.utf16.endIndex, to: offsetIndex_utf16)
textDocumentProxy.adjustTextPosition(byCharacterOffset: offset)
}
else {
textDocumentProxy.adjustTextPosition(byCharacterOffset: offset)
}
}
UPD: A messy hack, to try and fix Safari inconsistency. Since there's no way to discriminate between safari and not safari, to act based on result looks like the only solution.
func adjustCaretPosition(offset: Int) {
// for convenience
let textAfterCaret = textDocumentProxy.documentContextAfterInput ?? ""
let textBeforeCaret = textDocumentProxy.documentContextBeforeInput ?? ""
if let offsetIndex = offset > 0 ? textAfterCaret.index(textAfterCaret.startIndex, offsetBy: offset, limitedBy: textAfterCaret.endIndex) : textBeforeCaret.index(textBeforeCaret.endIndex, offsetBy: offset, limitedBy: textAfterCaret.startIndex),
let offsetIndex_utf16 = offsetIndex.samePosition(in: offset > 0 ? textAfterCaret.utf16 : textBeforeCaret.utf16)
{
// part of context before caret adjustment
let previousText = offset > 0 ? textAfterCaret : textBeforeCaret
// what we expect after adjustment
let expectedText = offset > 0 ? String(textAfterCaret[offsetIndex..<textAfterCaret.endIndex]) : String(textBeforeCaret[textBeforeCaret.startIndex..<offsetIndex])
// offset in UTF-16 characters
let offset_utf16 = offset > 0 ? textAfterCaret.utf16.distance(from: textAfterCaret.utf16.startIndex, to: offsetIndex_utf16) : textBeforeCaret.utf16.distance(from: textBeforeCaret.utf16.endIndex, to: offsetIndex_utf16)
// making adjustment
textDocumentProxy.adjustTextPosition(byCharacterOffset: offset)
// part of context after caret adjustment
let compareText = offset > 0 ? textAfterCaret : textBeforeCaret
// rollback if got unwanted results
// then adjust by grapheme clusters offset
if compareText != "", expectedText != compareText, compareText != previousText {
textDocumentProxy.adjustTextPosition(byCharacterOffset: -offset_utf16)
textDocumentProxy.adjustTextPosition(byCharacterOffset: offset)
}
}
else {
// we probably stumbled upon a textDocumentProxy inconsistency, i.e. context got divided by an emoji
// adjust by grapheme clusters offset
textDocumentProxy.adjustTextPosition(byCharacterOffset: offset)
}
}
Upvotes: 0
Reputation: 3181
Swift 5, it seems that the following code works well on iOS 12.
let count: Int = String(text).utf16.count
textDocumentProxy.adjustTextPosition(byCharacterOffset: count)
Upvotes: 4