Mischa
Mischa

Reputation: 17251

SwiftUI View not updating with binding to computed property on ObservableObject

I have a very simple SwiftUI view that only shows a TextField. The text field's text is bound to the string property of my viewModel that I instantiate as a @StateObject:

struct ContentView: View {
    @StateObject private var viewModel = ViewModel()

    var body: some View {
        TextField("Placeholder", text: $viewModel.string)
            .padding()
    }
}

The one thing that's "out of the ordinary" is that my string property is not a @Published property, but a simple computed property. In its setter, it sets the displayedString property to a fixed string (for testing purposes) which is a @Published property:

class ViewModel: ObservableObject {
    @Published var displayedString: String = ""

    var string: String {
        get { displayedString }
        set { displayedString = "OVERRIDE" }
    }
}

So when I type a string into the text field, I would expect the setter to trigger a view update (as it updates a @Published property) and then in the view, the text field should be updated with the displayedString. So in other words: No matter what I type, the text field should always show the string OVERRIDE.

But that's not the case!

It works for the very first letter I type, but then I can freely type anything into the text field.

For example, if I launch the app with only this view and type the string 123456789 this is what is displayed on the text field: OVERRIDE23456789

Why is that?

And how can I get the text field to always be up-to-date to what's set on the viewModel? (I know, I can just hook the text field to a @Published property directly, but I'm doing this computed property stuff for a reason and want to understand why it doesn't work as expected.)


Additional Observation:

When I add a Text label above the TextField and just make it show the viewModel.string, it shows the correct (expected) string:

var body: some View {
    Text(viewModel.string)
    TextField("Placeholder", text: $viewModel.string)
        .padding()
}

Screenshot

Upvotes: 3

Views: 1808

Answers (1)

Joseph Levy
Joseph Levy

Reputation: 222

I’m not sure but I think a custom binding might work for you

In the ViewModel use

var string: Binding<String> { Binding(
    get: { displayedString },
    set: { displayedString = “OVERRIDE” } )}

Then in body use: viewModel.string instead of $viewModel.string in the TextField and use: viewModel.string.wrappedValue in the text view

Stuf

Upvotes: 1

Related Questions