I'm coming back to iOS development after years away and diving head-first into SwiftUI. It's a lot of fun, but I've hit a brick wall.
The scenario is I have a main view (which itself is a tabview, not important other than that it's not the top-level of the view hierarchy). This has subviews that rely on data coming back from a REST call to the cloud, but then some subviews need to turn around and make subsequent network calls to set up websockets for realtime updates.
In the main view's .onAppear, I fire off an async REST call, it returns JSON that gets parsed into a ModelView.
The ViewModel is declared in the top view like this:
class ViewModel: ObservableObject {
@Published var appData = CurrentREST() // Codables from JSON
@State var dataIsLoaded : Bool = false
func fetchData() async {
await _ = WebService().downloadData(fromURL: "current") { currentData in
DispatchQueue.main.async {
self.appData = currentData
self.dataIsLoaded = true
}
}
}
}
The main view declares the model view:
struct HomeTabView: View {
@ObservedObject var viewModel = ViewModel()
@Binding private var dataReceived: Bool
...
}
In the toplevel view, the REST call is triggered like this:
.onAppear {
if !viewModel.dataIsLoaded {
Task {
await viewModel.fetchData()
DispatchQueue.main.async {
self.dataReceived = true
}
}
}
}
The viewModel gets passed down to subviews so they can update themselves with the returned data. That part all works fine.
But it's the next step that break down. A subview needs to go back to the server and set up subscriptions to websockets, so it can do realtime updates from then on. It's this second step that is failing.
The dataReceived binding is set to true when the REST call has completed. The viewModel and dataReceived flags are passed down to the subviews:
SummaryView(viewModel: viewModel, dataIsLoaded: self.dataReceived)
What needs to happen next is inside the subview to call a function to wire up the next websocket steps. I've tried setting up:
struct SummaryView: View { @ObservedObject var viewModel: ViewModel @State var dataIsLoaded: Bool = false ... }.onChange(of: dataIsLoaded) { setupWebSocket() }
Problem is, the onChange never gets called.
I've tried various permutations of setting up a @State and a @Binding on the view model, and a separate @State on the main view. None of them get called and the subview's function that wires up the websockets never gets called.
The basic question is:
How do you trigger a cascading series of events through SwiftUI so external events (a network call) can cascade down to subviews and from there, their own series of events to do certain things.
I haven't gone deep into Combine yet, so if that's the solution, I'll go there. But I thought I'd ask and see if there was a simpler solution.
Any suggestions or pointers to best practices/code are most appreciated.