Brinkster
Brinkster

Reputation: 314

SwiftUI ObservableObject with published array of Structs does not notify view of change

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!

Upvotes: 0

Views: 570

Answers (1)

lorem ipsum
lorem ipsum

Reputation: 29614

@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)

Upvotes: 2

Related Questions