I am creating a sample app in SwiftUI and changes to one of my model’s properties are not causing the view to update which it should. I have already tested that the network call is working as expected and the model is being updated with the new text but it isn’t causing the view to be rebuilt and show the new text. I am passing my viewModel through the App object. The text view always shows "initial value" so What might be happening?
import SwiftUI
import Foundation
class ViewModel: ObservableObject {
@Published var model = Model()
var factText: String {
return model.factText
}
func getNewFact() {
model.networkCall()
}
}
struct ContentView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
VStack {
Button("New Fact") {
viewModel.getNewFact()
// print(viewModel.model.factText)
}
Text(viewModel.model.factText)
}
.padding()
}
}
class Model {
var factText = "initial value"
func networkCall() {
let headers:HTTPHeaders = ["X-Api-Key": Constants.apiKey]
AF.request(Constants.url, headers: headers)
.responseDecodable(of: [Response].self) {response in
self.factText = response.value![0].fact
}
}
}
@main
struct FunFactsWatchApp_Watch_AppApp: App {
var game = ViewModel()
var body: some Scene {
WindowGroup {
ContentView(viewModel: game)
}
}
}
>Solution :
Reference types like classes cannot be @Published because the instance (pointer) doesn’t change when a property is modified (actually they can but this is not needed and beyond the question).
Model must be a struct
struct Model {
var factText: String // can even be `let`
}
the network call must be in the view model
class ViewModel: ObservableObject {
@Published var model = Model(factText: "")
func networkCall() {
let headers:HTTPHeaders = ["X-Api-Key": Constants.apiKey]
AF.request(Constants.url, headers: headers)
.responseDecodable(of: [Response].self) {response in
self.model = Model(factText: response.value![0].fact)
}
}
}
and you have to create and initialize a @StateObject in ContentView
struct ContentView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
VStack {
Button("New Fact") {
viewModel.networkCall()
}
Text(viewModel.model.factText)
}
.padding()
}
}