Hey, I have an issue with a certain behaviour of TextField in SwiftUI. I created a custom ParseableFormatStyle and pass it to formatted in the getter for the text just like below.
TextField("", text: .init(
get: {
textValue.formatted(format)
}, set: { value in
textValue = value
})
)
and in formatter I just chunk the input to get "AB AD AC" from "ABADAC" for example.
public struct ChunkedFormatStyle: ParseableFormatStyle {
public var parseStrategy: DefaultParseStrategy {
DefaultParseStrategy()
}
private let distance: Int
public init(distance: Int) {
self.distance = distance
}
// returns a string with a white space after every 2 characters.
public func format(_ value: String) -> String {
value.chunked(into: distance)
}
}
public struct DefaultParseStrategy: ParseStrategy {
public func parse(_ value: String) throws -> String {
value
}
}
private extension String {
func chunked(into distance: Int) -> Self {
self.lazy
.filter { $0 != " " }
.chunks(ofCount: distance)
.map { String($0) }
.joined(separator: " ")
}
}
The issue is that when typing text in the TextField, everything works fine, and the text dynamically changes; however, if I want to edit the text, the cursor automatically jumps to the beginning of the input. What can be done to prevent this from happening?
In a way, you're asking for the impossible here. Every time the user presses a key, you replace all of the text in the text field, so where should the cursor go? The text field doesn't "know" that the replacement text is similar to the text being replaced, so it can't put the cursor at a similar position for you.
In UIKit, you would solve this problem by using a delegate that watches the changes to the typed text, replaces it with formatted text, and then manually re-positions the cursor where it should be. However, I don't recommend that you pursue a UIKit workaround in this case, because (as mentioned on Stack Overflow), this can potentially be a troublesome UI for the user.
What can go wrong is that the user's knowledge of what keys they've pressed so far is contradicted by the actual text. For example, if I just typed "AAB" and realize I meant "ABB", I would expect to be able to correct this by pressing the delete
key twice. If I have to press the delete
key three times because there's a space there now, I'm likely to end up mis-typing my correction.
You can try to program around this by moving the cursor in more complicated ways depending on the edits, but in the end it's likely to be frustrating for you as developer, and incorrect/unexpected for the user.
My personal preference in cases like this is to preserve exactly what the user typed in the editable text field, and to display the formatted equivalent as non-editable text just below. That's the best of both worlds for the user — they can see what they're typing and have typed, and they can see what the formatted equivalent is — and for you too, because you don't need to write any tricky code.