High Level Problem I have a view model which is hooked into Firebase which updates a published array of structs. For whatever reason it does not notify views depending on this Published variable of its change.
Code
View Model
@MainActor class RestaurantsViewModel: ObservableObject {
@Published var restaurants: [Restaurant]
private var db = Firestore.firestore()
init() {
self.restaurants = []
db.collection("Restaurants").addSnapshotListener(includeMetadataChanges: true) { [self] (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
let newRestaurants = documents.map { queryDocumentSnapshot -> Restaurant in
/* do stuff */
}
self.restaurants = newRestaurants
}
}
}
Restaurant
struct Restaurant: Identifiable {
let id: String
let name: String
let location: CLLocation
let votes: Int
init(id: String, name: String, location: CLLocation, votes: Int) {
self.id = id
self.name = name
self.location = location
self.votes = votes
}
}
View (which I expect to be updating)
struct RestaurantsView: View {
// This is the same instance of the view model as the one recieving the database refreshes.
// I've checked by printing the memory addresses of the view model in both places.
@EnvironmentObject var restarauntViewModel: RestaurantsViewModel
var body: some View {
VStack(content: {
Text("")
VStack(content: {
ScrollView {
VStack(content: {
ForEach(restarauntViewModel.restaurants, id: \.id) { restaurant in
RestaurantView(restaurant: restaurant)
}
})
}
})
})
}
}
struct RestaurantView: View {
@State var restaurant: Restaurant
var body: some View {
HStack(content: {
NameView(name: restaurant.name)
VoteView(voteCount: restaurant.votes)
})
.onTapGesture {
withAnimation {
/* do stuff */
}
}
}
}
Question
Am I overlooking an obvious reason why the ForEach
in my RestaurantsView
is not updating when I receive a an update from my database?
Thanks in advance for any help!
@State
is a source of truth and should always be marked private
, something like
@State private var restaurant: Restaurant = .init()
What you need is a @Binding
@Binding var restaurant: Restaurant
Which is a two way connection.
You implementation now makes a copy of the object and it looses connection to the parent.
The @State
prevents any redrawing because SwiftUI uses it to maintain the view’s identity.
Your ForEach
needs some minor changes.
ForEach($restarauntViewModel.restaurants, id: \.id) { $restaurant in
RestaurantView(restaurant: $restaurant)