Search code examples
swiftswiftui

Given a value that's bound in two places, modify only one of them


Updated to simplify example

I want to change the title text, when either I type in the textfield or tap the button. However I don't want to change the TextField text when I tap the button, just want to change the title text. This is the demo code

struct HobbitView: View {
    @State private var hobbitName = "Bilbo"
    
    var body: some View {
        
        //"Place Holder" should not be modified by tapping the button
        TextField("Place Holder", text: $hobbitName)
        
        //Should be modifiable by TextField or Button
        Text("The name is \(hobbitName).")

         //Should only modify the Text, not the TextField text
        Button("Set a Name") {
            hobbitName = "Sam"
        }
    }
}

I guess you may call it "conditional binding". I don't know how else to call it.

I'm new to SwiftUI and I think I'm missing some key knowledge on how SwiftUI bindings work.


Solution

  • You need an additional @State to store the text field's text.

    Set this to hobbit.firstName initially, and when it changes, update hobbit.firstName.

    @StateObject private var hobbit = Hobbit()
    
    @State private var textFieldText = ""
    
    var body: some View {
        TextField("Place Holder", text: $textFieldText)
            .padding(.bottom)
            .onAppear {
                textFieldText = hobbit.firstName
            }
            .onChange(of: textFieldText) { _, newValue in
                hobbit.firstName = newValue
            }
        
        
        Text("The name is \(hobbit.firstName).")
            .font(.title)
    
        
        Button("Set a Name") {
            hobbit.firstName = "Sam"
        }
    }
    

    You can wrap this into a custom TextField:

    struct HobbitView: View {
        @StateObject private var hobbit = Hobbit()
        
        var body: some View {
            CustomTextField(text: $hobbit.firstName)
    
            Text("The name is \(hobbit.firstName).")
                .font(.title)
            Button("Set a Name") {
                hobbit.firstName = "Sam"
            }
        }
    }
    
    struct CustomTextField: View {
        @State private var textFieldText = ""
        @Binding var text: String
        
        var body: some View {
            TextField("Place Holder", text: $textFieldText)
                .padding(.bottom)
                .onAppear {
                    textFieldText = text
                }
                .onChange(of: textFieldText) { _, newValue in
                    text = newValue
                }
        }
    }