swiftuiobservableios17bindablexcode16

Sharing Data Between Swift Views?


I am trying to get my head around sharing data with multiple views using @Obervable protocol but having trouble making it work.

I have created a simple app to demonstrate the issue I am having, which you will see. It comprises of the:

The trouble I am having is passing the data through to the ‘SecondView’ All works fine within the ContentView, but outside that, no good.

Can someone have a look at this simple code and tell me where I have got it all wrong.

Pete

DemoApp.swift

import SwiftUI

@main
struct DemoApp: App {
    @State private var data = ModelData()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(data)
        }
    }
}

ContentView.swift

import SwiftUI

struct ContentView: View {
    @Bindable var data = ModelData()
    
    var body: some View {
        NavigationStack {
            VStack {
                TextField("Value", text: $data.value)
                Text("\(data.value)")
                
                NavigationLink {
                    SecondView()
                        .onAppear() {
                            data.Compute()
                        }
                } label: {
                    Text("Button")
                }
            }
        }
    }
}

#Preview {
    ContentView()
}

Model.swift

import Foundation
import Observation

@Observable
class ModelData {
    var value = ""
    
    func Compute() {
        print(self.value)
    }
}

SecondView.swift

import SwiftUI

struct SecondView: View {
    var data = ModelData()

    var body: some View {
        Text("You Typed: \(data.value)")
    }
}

#Preview {
    SecondView()
}

RESULTS AFTER CHANGE

I made the suggested changes and they resolved the issue, but now, as soon as I entered: @Environment (ModelData.self) var data in the 'ContentView', I am now unable to preview the View.

I end up receiving this fatal error: Fatal Error

Any ideas ?


Solution

  • Currently you have multiple ModelData() that have no relations to each other. You need to pass a single source of truth, the ModelData() in your DemoApp to the other views.

    So try this approach:

    import Foundation
    import SwiftUI
    
    
    struct ContentView: View {
        @Environment(ModelData.self) var data // <--- here
    
        var body: some View {
            @Bindable var data = data  // <--- here
            NavigationStack {
                VStack {
                    TextField("Value", text: $data.value)
                    Text("\(data.value)")
                    
                    NavigationLink {
                        SecondView()
                            .onAppear() {
                                data.Compute()
                            }
                    } label: {
                        Text("Button")
                    }
                }
            }
        }
    }
    
    @Observable
    class ModelData {
        var value = ""
        
        func Compute() {
            print(self.value)
        }
    }
    
    struct SecondView: View {
        @Environment(ModelData.self) var data // <--- here
    
        var body: some View {
            Text("You Typed: \(data.value)")
        }
    }
    

    See this link Managing model data in your app

    EDIT-1:

    Alternatively if you want to edit the data, you could use this setup for SecondView

    struct SecondView: View {
        @Bindable var data: ModelData
    
        var body: some View {
            Text("You Typed: \(data.value)")
            TextField("", text: $data.value).border(.red)
        }
    }
    

    And in ContentView call SecondView(data: data)