Search code examples
viewbindingswiftuistatereusability

Reuse views without passing bindings to every intermediate view


In swiftUI it's fairly simple to create single-use views with their according States embedded in an associated view-model. But how can I use the e.g. custom textfield of view1, which is embedded in a more complex view2, in view3 which is the final view? I know that I could pass the textfield's text of view1 as binding to view2 and then to view3. But as soon as the UI gets complex a lot of bindings need to bee passed, especially if there are a lot of intermediate views. How could I reuse view1 without passing it's textfield text state to every intermediate view, and how could this ideally be implemented into the view-model of view3?

Simple example structure:

View1:

struct SwiftUIView3: View {

    @Binding var textInTextField: String

    var body: some View {
        TextField("Test", text: $textInTextField)
    }
}

View2:

struct SwiftUIView2: View {

    @Binding var textInTextField: String

    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 15)
                .frame(height: 200)
                .foregroundColor(.blue)
            SwiftUIView3(textInTextField: $textInTextField)
        }
    }
}

View3:

struct ContentView: View {

    @State private var textInTextField = ""

    var body: some View {
        VStack {
            SwiftUIView2(textInTextField: $textInTextField)
            Text(textInTextField)
        }
    }
 }

Solution

  • Here is possible solution. Tested with Xcode 11.4 / iOS 13.4

    The idea is to use view model injected as environment object into sub-hierarchy, so in any level subview can extract it and use, the top-level view will be updated correspondingly `cause it holds this view model as observed object.

    struct SwiftUIView3: View {
        @EnvironmentObject var eo: TextViewModel  //  << auto-associated
    
        var body: some View {
            TextField("Test", text: $eo.textInTextField)
        }
    }
    
    struct SwiftUIView2: View {
        var body: some View {
            ZStack {
                RoundedRectangle(cornerRadius: 15)
                    .frame(height: 200)
                    .foregroundColor(.blue)
                SwiftUIView3()       //   << nothing to pass
            }
        }
    }
    
    class TextViewModel: ObservableObject {
        @Published var textInTextField = ""
    }
    
    struct ContentView: View {
        @ObservedObject private var vm = TextViewModel()
    
        var body: some View {
            VStack {
                SwiftUIView2()       //  << nothing to pass
                Text(vm.textInTextField)
            }.environmentObject(vm)      // << inject into hierarchy
        }
     }