skip to Main Content

Scenario:
In SwiftUI, my TextField has a Double value. On tapping/focusing the TextField the current Double value is cleared, to allow the user to easily enter a new value without having to select and delete the current value. If the user does not enter a new value, the old value is restored on deselect.

Issue

Utilising .focused() and .onChange(of:) this does not appear to be possible. Below code correctly clears the value (visible in console). However, the change is not reflected within the UI. Forcing the UI to update by utilising .id() on the TextField deselects the TextField, making this workaround useless.

Why does the UI not update from the change to the TextField value property?

struct ContentView: View {
    
    @FocusState var focus: Bool
    @State var test: Double? = 100
    @State var id: UUID = UUID()
    
    var body: some View {
        VStack {
            TextField("Placeholder", value: $test, format: .number)
                .id(id)
                .focused($focus)
                .onChange(of: focus, perform: { editing in
                    var oldValue: Double? = test
                    if editing {
                        self.$test.wrappedValue = nil
                        self.$id.wrappedValue = UUID()
                    } else {
                        if test == nil {
                            self.$test.wrappedValue = oldValue
                            self.$id.wrappedValue = UUID()
                        }
                    }
                })
        }
        .padding()
    }
}

Is there a way to do this with SwiftUI‘s TextField?

2

Answers


  1. Chosen as BEST ANSWER

    Extending on @meomeomeo' answer, please find a reusable TextField view below that satisfies the need for the TextField text to be of type Double.

    The second .onChange(of:) updates the input binding as the user is typing but only if the typed text is of type Double. This is in case the user never deselects the TextField before e.g. saving.

    struct TextFieldDoubleClearView: View {
        
        @FocusState var focus: Bool
        
        @Binding var input: Double
        
        @State var value: String = "100"
        @State private var oldValue: String = ""
        
        var body: some View {
            VStack {
                TextField("Placeholder", text: $value)
                    .keyboardType(.decimalPad)
                    .focused($focus)
                    .onChange(of: focus, perform: { editing in
                        if editing {
                            if !value.isEmpty {
                                oldValue = value
                                value = ""
                            }
                        } else {
                            if value.isEmpty {
                                value = oldValue
                            } else {
                                if let valueDouble: Double = Double(value) {
                                    input = valueDouble
                                } else {
                                    value = oldValue
                                }
                            }
                        }
                    })
                    .onChange(of: value, perform: { _ in
                        if let valueDouble: Double = Double(value) {
                            input = valueDouble
                        }
                    })
            }
            .padding()
            .onAppear {
                value = String(input)
            }
        }
    }   
    

  2. Why UI not update from the change to the TextField value ?

    Because it doesn’t bind TextField’s value (String) to your value (Double) directly.

    To clear value on focus, restore on deselect, you need to do it on TextField with text, not value. Here’s an example:

    struct TestView: View {
        @FocusState var focus: Bool
        @State var value: String = "100"
        @State private var oldValue: String = ""
        
        var body: some View {
            VStack {
                TextField("", text: .constant("other"))
                TextField("Placeholder", text: $value)
                    .focused($focus)
                    .onChange(of: focus) { editing in
                        if editing {
                            if !value.isEmpty {
                                oldValue = value
                                value = ""
                            }
                        } else {
                            if value.isEmpty {
                                value = oldValue
                            }
                        }
                    }
            }
            .textFieldStyle(.roundedBorder)
            .padding()
        }
    }
    

    If you want to make it Double only, handle it outside (in ViewModel or something)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search