SwiftUI - Binding in ObservableObject

Viewed 41

Let's say we have a parent view like:

struct ParentView: View {
    @State var text: String = ""

    var body: some View {
        ChildView(text: $text)
    }
}

Child view like:

struct ChildView: View {

    @ObservedObject var childViewModel: ChildViewModel

    init(text: Binding<String>) {
        self.childViewModel = ChildViewModel(text: text)
    }

    var body: some View {
        ...
    }
}

And a view model for the child view:

class ChildViewModel: ObservableObject {
    @Published var value = false
    @Binding var text: String

    init(text: Binding<String>) {
        self._text = text
    }

    ...
}

Making changes on the String binding inside the child's view model makes the ChildView re-draw causing the viewModel to recreate itself and hence reset the @Published parameter to its default value. What is the best way to handle this in your opinion?

Cheers!

1 Answers

The best way is to use a custom struct as a single source of truth, and pass a binding into child views, e.g.

struct ChildViewConfig {
    var value = false
    var text: String = ""

    // mutating funcs for logic
    
    mutating func reset() {
        text = ""
    }
}

struct ParentView: View {
    @State var config = ChildViewConfig()

    var body: some View {
        ChildView(config: $config)
    }
}

struct ChildView: View {

    @Binding var config: ChildViewConfig

    var body: some View {
        TextField("Text", text: $config.text)
        ...
        Button("Reset") {
            config.reset()
        }
    }
}

"ViewConfig can maintain invariants on its properties and be tested independently. And because ViewConfig is a value type, any change to a property of ViewConfig, like its text, is visible as a change to ViewConfig itself." [Data Essentials in SwiftUI WWDC 2020].

Related