Wrong cursor behaviour during updating TextField input in SwiftUI

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?

Answered by DTS Engineer in 775016022

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.

I forgot to mention about to update input dynamically

.onChange(of: textValue) { oldValue, newValue in
            textValue = oldVlaue
 }
Accepted Answer

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.

Wrong cursor behaviour during updating TextField input in SwiftUI
 
 
Q