Hi!
I am trying to create a simple SwiftUI TextField, with an external button to add text to the field at the current insertion point (the cursor in the TextField).
When I add the text, the cursor (I-Beam) remains at the original insertion point, so I want it to move over to the end of what I added. The trouble is, it sometimes moves further forward or to the end (visibly) but works as if it is still at the point I moved it to. This seems to possibly be due to emojis in the TextField (because, I assume, they are composed of more bytes).
Further, sometimes the addition of the text can cause an emoji to appear unexpectedly, I assume because it is combining the bytes in an odd way. So moving the cursor seems to sometimes introduce weird behaviour.
This comes from a much larger project, but I have distilled this down to the smallest example project I could. And I have a video to show how it behaves.
Here's the main part of the code, and I'll attach an Xcode project:
import SwiftUI
struct ContentView: View {
@State private var text: String = "abcdef🧁🧁🧁🧁ghijkl"
@State private var selectedText: TextSelection?
var body: some View {
VStack {
TextField("", text: $text, selection: $selectedText)
.font(.title)
Button("Add Z at Insertion Point in TextField") {
// Get indices of any selection in the text field
let from: String.Index, to: String.Index
if let selectedText = selectedText {
let indices = selectedText.indices
switch indices {
case .selection(let range):
from = range.lowerBound
to = range.upperBound
case .multiSelection(let rangeSet):
from = rangeSet.ranges.first!.lowerBound
to = rangeSet.ranges.first!.upperBound
default:
from = self.text.endIndex
to = self.text.endIndex
}
} else {
from = self.text.endIndex
to = self.text.endIndex
}
guard from <= to && from <= self.text.endIndex else { return }
// Insert and update the cursor position
self.text.replaceSubrange(from..<to, with: "Z")
// Move cursor after the inserted character
let newIndex = self.text.index(after: from)
selectedText = TextSelection(insertionPoint: newIndex)
}
}
.padding()
}
}
STEPS TO REPRODUCE Run the app. Also view the video as it shows the steps.
- Put insertion point between c and d.
- Press the "Add Z" button. Note that Z is placed between c and d. This is great.
- Put insertion point between h and i.
- Press the "Add Z" button. Note that Z is placed between h and i. BUT, the insertion point I-beam moves to the end of the string.
- Press the "Add Z" button again. Z is added where you would have expected based on where the TextSelection insertion point was put, but the flashing I-Beam is still at the end.
- Press the "Add Z" button again. Same issue. insertion point is being shown at end, but to the button it is between Z and i. OF NOTE, if you use the keyboard and press delete, it deletes from end (where the I-beam is).
- Now put insertion point between the 4 cupcakes.
- Press "Add Z" two times. It behaves correctly.
- Press "Add Z" a third time. It adds a fairy emoji.
So, any idea what I am doing wrong? I thought it might be an issue requiring me to update in a background thread, but I tried that, even delaying the update in the thread, but the issue remains.
Thanks in advance.
Here's a video: https://curmi.name/temp/SimpleTextField%20showing%20issues.mp4
And if it helps, here is the Xcode project: https://curmi.name/temp/SimpleTextfield.zip