Bagusflyer
Bagusflyer

Reputation: 12915

didSet for a @Binding var in Swift

Normally we can use didSet in swift to monitor the updates of a variable. But it didn't work for a @Binding variable. For example, I have the following code:

@Binding var text {
   didSet {
       ......
   }
}

But the didSet is never been called.Any idea? Thanks.

Upvotes: 33

Views: 14259

Answers (3)

pawello2222
pawello2222

Reputation: 54426

iOS 17+

Use onChange with a zero or two-parameter closure:

struct ContentView: View {
    @Binding var counter: Int

    var body: some View {
        Text(counter, format: .number)
            .onChange(of: counter) {
                print("onChange")
            }
            .onChange(of: counter) { oldValue, newValue in
                print("onChange: \(oldValue), \(newValue)")
            }
    }
}

iOS 14+

Use onChange with a one-parameter closure:

struct ContentView: View {
    @Binding var counter: Int
    
    var body: some View {
        Text(String(counter))
            .onChange(of: counter) { value in
                print("onChange: \(value)")
            }
    }
}

iOS 13+

Use onReceive - a universal solution for all SwiftUI versions:

struct ContentView: View {
    @Binding var counter: Int

    var body: some View {
        Text(String(counter))
            .onReceive(Just(counter)) { value in
                print("onReceive: \(value)")
            }
    }
}

Upvotes: 30

McKinley
McKinley

Reputation: 1394

The best way is to wrap the property in an ObservableObject:

final class TextStore: ObservableObject {
    @Published var text: String = "" {
        didSet { ... }
   }
}

And then use that ObservableObject's property as a binding variable in your view:

struct ContentView: View {
    @ObservedObject var store = TextStore()
    var body: some View {
        TextField("", text: $store.text)
    }
}

didSet will now be called whenever text changes.


Alternatively, you could create a sort of makeshift Binding value:

TextField("", text: Binding<String>(
    get: {
        return self.text
    },
    set: { newValue in
        self.text = newValue
        ...
    }
))

Just note that with this second strategy, the get function will be called every time the view is updated. I wouldn't recommend using this approach, but nevertheless it's good to be aware of it.

Upvotes: 0

rob mayoff
rob mayoff

Reputation: 385500

You shouldn’t need a didSet observer on a @Binding.

If you want a didSet because you want to compute something else for display when text changes, just compute it. For example, if you want to display the count of characters in text:

struct ContentView: View {
    @Binding var text: String

    var count: Int { text.count }

    var body: some View {
        VStack {
            Text(text)
            Text(“count: \(count)”)
        }
    }
}

If you want to observe text because you want to make some other change to your data model, then observing the change from your View is wrong. You should be observing the change from elsewhere in your model, or in a controller object, not from your View. Remember that your View is a value type, not a reference type. SwiftUI creates it when needed, and might store multiple copies of it, or no copies at all.

Upvotes: 13

Related Questions