Why doesn't my view update ObservedObject inside a different View in SwiftUI?

114 views Asked by At

My ObservedObject will not connect with another view.

I have:

class AppState: ObservableObject {
    @Published var destination: String = ""
    @Published var arrivalTime = Date()
    @Published var isLoading: Bool = false
    @Published var cleanedTafString: String = ""
}

which declares the objects to be updated.

My main view, where I make the connection with my AppState() class, once the button "GO" is pressed, the func LoadingProcess() is executed which is inside of a Task, runs an async function runAllFunctions() in a seperate swift file. Once That is complete, $readyToNavigate (@State private var) triggers .navigationDestinationto change View to ResultsScreenView():

struct InitialScreenView: View {
    
    @ObservedObject private var app = AppState()
    @State private var currentTimeDate = Date.now
    @State private var readyToNavigate : Bool = false
    
    var body: some View {
        NavigationStack {
            ZStack {
                VStack {
                    VStack {
                        HStack {
                            Text(DateFormatterView.currentLocalDateFormatted())
                        }
                        .padding(2)
                        Text(DateFormatterView.currentGMTDateFormatted())
                    }
                    HStack {
                        TextFieldView(preFill: "Destination", fill: $app.destination)
                    }
                    Button(action: LoadingProcess, label: {
                        Text("GO")
                    })
                        .navigationDestination(isPresented: $readyToNavigate) {
                            ResultsScreenView()}
                        .navigationTitle("WxStrip")
                }
                if app.isLoading {
                    LoadingView()
                }
            }
        }
    }
    func LoadingProcess() {
        Task {
            app.isLoading = true
            let resultString = await app.runAllFunctions()
            app.isLoading = false
            // Update the observed object and trigger a refresh of the view
            app.cleanedTafString = resultString
            app.objectWillChange.send()
            readyToNavigate = true
            print(app.cleanedTafString)
        }
    }
}

Once the runAllFunctions() function has finished inside the Task, I am telling app.cleanedTafString to update with the string that is returned from runAllFunctions().

This works fine for the current view if I wanted to display it in InitialScreenView, but I don't. I need it on a separate View called ResultsScreenView.

struct ResultsScreenView: View {

    @ObservedObject private var app = AppState()

    var body: some View {
        ZStack {
            Button("Push me", action: {
                DisplayCleaned()
            })
        }
    }
    func DisplayCleaned() {
        print("Printout \(app.cleanedTafString)")
    }
}

Ideally I want to display the string in a Text() on screen but for troubleshooting have just called a function on press of the button....

This returns an empty string every time. It doesn't matter which of the @Published var I try to print out, it just doesn't link somehow.

What is weird is that, on the initial declaration of the @Published var if I enter some text instead of an empty string, it will print that instead of nothing. It's as though ResultsScreenView is only ever looking at the initial declaration of the @Published var and not seeing the updates. Although I know for sure they update as I can display it in print statements and in Text() fields on screen in the InitialScreenView....

1

There are 1 answers

2
Roland Lariotte On BEST ANSWER

As for working with ObservableObject, you must first declare your object with the @StateObject property wrapper. It would initialize your object once and for all, and keep its state in memory even if the SwiftUI view refresh.

Then, if you need to pass its state through child view, we will declare it as a @ObservedObject, but do not initialize it the way you did it in ResultsScreenView.

struct InitialScreenView: View {

  @StateObject private var app = AppState()

  // Rest of your code
}

struct ResultsScreenView: View {

  @ObservedObject var app: AppState

  // Rest of your code
}

You will have to pass your object through the ResultsScreenView initializer:

ResultsScreenView(app: app)

As a reminder, it works the same way as @State and @Binding, but for objects.