0

Could you please help me with clear button code, that doesn't work properly?

I have a TextField, which stores an input of type Double, and unfortunately the classic solution of including additional modifier is not working.

Here is my code:

import SwiftUI

struct ContentView: View {
    
    @State private var amount: Double = 10
    @FocusState private var isFocused: Bool
    
    var body: some View {
        NavigationView {
            Form {
                Section {
                    TextField("Amount to pay", value: $amount, format: .currency(code: "USD"))
                        .keyboardType(.decimalPad)
                        .focused($isFocused)
                        .modifier(TextFieldClearButton(amount: $amount, focus: $isFocused))
                }
            }
        }
        .toolbar {
            ToolbarItemGroup(placement: .keyboard) {
                
                Spacer()
                
                Button("Done") {
                    isFocused = false
                }
            }
        }
    }
}

struct TextFieldClearButton: ViewModifier {
    @Binding var amount: Double
    @FocusState.Binding var focus: Bool
    
    func body(content: Content) -> some View {
        HStack {
            content
            
            if amount != 0.0 {
                Button(
                    action: {
                        self.amount = 0.0
                        if focus == false {
                            focus = true
                        }
                    },
                    label: {
                        Image(systemName: "delete.left")
                            .foregroundColor(Color(UIColor.opaqueSeparator))
                    }
                )
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Looks like the view is not updated when I am calling the modifier, so I can see only default value. How can I fix this?

1

1 Answer 1

0

I have found that while a TextField is focused, you can't externally update the value. It simply ignores it. So, in order to get the Textfield to update while focused, you have to cause a view refresh as well. The simplest way of doing this is to put a .id() on the TextField with a value you can change like this:

struct ButtonClearTextField: View {
    // Your Double is now optional
    @State private var amount: Double? = 10
    @FocusState private var isFocused: Bool
    @State var updater = UUID()
    
    var body: some View {
        Form {
            Section {
                TextField("Amount to pay", value: $amount, format: .currency(code: "USD"))
                    .keyboardType(.decimalPad)
                    .focused($isFocused)
                    // Put the id in the view. I put it here as it helps indicate what is changing.
                    .id(updater)
                    // textFieldClearButton is a func on View as a result of the extension
                    .textFieldClearButton(amount: $amount, focus: $isFocused, updater: $updater)

                // I added this button so you could test that if
                // the TextField is focused it won't change.
                Button {
                    amount = 0
                } label: {
                    Text("Clear")
                }
                
            }
        }
        .toolbar {
            ToolbarItemGroup(placement: .keyboard) {
                
                Spacer()
                
                Button("Done") {
                    isFocused = false
                }
            }
        }
    }
}

struct TextFieldClearButton: ViewModifier {
    @Binding var amount: Double?
    @FocusState.Binding var focus: Bool
    @Binding var updater: UUID
    
    func body(content: Content) -> some View {
        HStack {
            content
            
            if amount != 0.0 {
                Button(
                    action: {
                        // Setting to nil clears the field
                        amount = nil
                        // After setting the value, 
                        updater = UUID()
                        DispatchQueue.main.async {
                            focus = true
                        }
                    },
                    label: {
                        Image(systemName: "delete.left")
                            .foregroundColor(Color(UIColor.opaqueSeparator))
                    }
                )
            }
        }
    }
}

// Creates func textFieldClearButton
extension View {
    func textFieldClearButton(amount: Binding<Double?>, focus: FocusState<Bool>.Binding, updater: Binding<UUID>) -> some View {
        self.modifier(TextFieldClearButton(amount: amount, focus: focus, updater: updater))
    }
}

In order to get the behavior that Asperi linked to, you simply need to use an optional value and set it to nil.

I also made a View extension to clean up the code at the call site a bit.

Sign up to request clarification or add additional context in comments.

2 Comments

Hello. Very useful solution, that solves my problem, I need to study more about using UUID(). However I have one issue - I guess that keyboard FocusState should be TRUE after user clicks on clear button. Now it doesn't work properly, actually your focus = true in Button action does not set the focus to TRUE, it remains FALSE. Unfortunately I have no idea, why this piece of code could not toggle the value of focus to TRUE.
I updated the code to add a DispatchQueue around the focus = true. The problem was a timing issue. When the updater changes, the view refreshes and without the DispatchQueue to slow things a bit, isFocused was being set to true, and then the view refresh was setting it back to false.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.