视图未使用 SwiftUI 中 Firestore 的数据更新

The view is not updated with data from Firestore in SwiftUI

我有一个小问题,我正在从 Firebase 和 Firestore 数据库中获取数据,但只有在我选择其中一个类别时才会更新视图。该视图有 4 个类别,可以包含项目。我会 post 在这个问题的末尾放一个简短的视频来说明我的问题是什么。

所以,这是我的 ViewModel :

class HomeViewModell : ObservableObject {
    
    
    @Published var productType : ProductType = .Wearable
    @Published var products = [Product]()
    @Published var filteredProducts : [Product] = []
    var searchCancellable: AnyCancellable?
    init() {
        getData()
        filteredProductByType()
    }
    func getData() {
        FirebaseManager.shared.firestore.collection("products").addSnapshotListener { querySnapshot, error in
            
            guard let documents = querySnapshot?.documents else {
                print("No documents")
                return
            }
            DispatchQueue.main.async {
                self.products = documents.map { d in
                    return Product(id: d.documentID, type: ProductType(rawValue: d["type"] as? String ?? "") ?? .Laptops
                                   , title: d["title"] as? String ?? "",
                                   subtitle: d["subtitle"] as? String ?? "",
                                   price: d["price"] as? String ?? "", productImage: d["productImage"] as? String ?? ""
                    )
                }
            }
        }
    }
    
    func filteredProductByType() {
        DispatchQueue.global(qos: .userInteractive).async {
            let results = self.products
            //Since it will require more memory so were using lazy to perform more
                .lazy
                .filter { product in
                    return product.type == self.productType
                }
            // Limiting results..
                .prefix(4)
            
            DispatchQueue.main.async {
                self.filteredProducts = results.compactMap({ product in
                    return product
                })
            }
        }
    }
}

这些是我的结构:

struct Product : Identifiable, Hashable {
    var id = UUID().uuidString
    var type : ProductType
    var title : String
    var subtitle : String
    var description : String = ""
    var price : String
    var productImage : String = ""
    var quantity : Int = 1
}

enum ProductType : String, CaseIterable {
    case Wearable = "Wearable"
    case Laptops = "Laptops"
    case Phones = "Phones"
    case Tablets = "Tablets"
}

这是我的观点


struct Homee: View {
    @Namespace var animation : Namespace.ID
    @EnvironmentObject var sharedData : SharedDataViewModel
    @ObservedObject var homeData : HomeViewModel = HomeViewModel()
    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
            VStack(spacing : 15) {
                ScrollView(.horizontal, showsIndicators: false) {
                    HStack(spacing : 18) {
                        ForEach(ProductType.allCases, id: \.self) { type in
                            productTypeView(type: type)
                        }
                    }
                    .padding(.horizontal, 25)
                }
                .padding(.top, 28 )
                ScrollView(.horizontal, showsIndicators: false) {
                    HStack(spacing : 25) {
                        ForEach(homeData.filteredProducts) { product in
                            ProductCardView(product: product)
                        }
                    }
                    .padding(.horizontal, 25)
                    .padding(.bottom)
                    .padding(.top, 80)
                }
                .padding(.top, 30)
            }
            .padding(.vertical)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color("HomeBG"))
        .onChange(of: homeData.productType) { newValue in
            homeData.filteredProductByType()
    }
    }

    @ViewBuilder
    func productTypeView(type : ProductType) -> some View {
        Button {
            withAnimation {
                homeData.productType = type
            }
        } label: {
            Text(type.rawValue)
                .font(.custom(customFont, size: 15))
                .fontWeight(.semibold)
                .padding(.bottom, 10)
                .overlay(
                    ZStack {
                        if homeData.productType == type {
                            Capsule()
                                .fill(Color("Purple"))
                                .matchedGeometryEffect(id: "PRODUCTTAB", in: animation)
                                .frame(height : 2)
                        } else {
                            Capsule()
                                .fill(Color.clear)
                                .frame(height : 2)
                        }
                    }
                        .padding(.horizontal, -5)
                    , alignment: .bottom
                )
        }
    }
    @ViewBuilder
    func ProductCardView(product : Product) -> some View {
        VStack(spacing: 10) {
           //Addid matched geometry effect
            ZStack {
                if sharedData.showDetailProduct {
                    Image(product.productImage)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                }
                else {
                    Image(product.productImage)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .matchedGeometryEffect(id: "\(product.id)IMAGE", in: animation)
                }
            }
                .frame(width: getRect().width / 2.5, height: getRect().width / 2.5)
                .offset(y: -80)
                .padding(.bottom, -80
            Text(product.title)
                .font(.custom(customFont, size: 18))
                .fontWeight(.semibold)
                .padding(.top)
            Text(product.subtitle)
                .font(.custom(customFont, size: 14))
                .fontWeight(.semibold)
                .foregroundColor(.gray)
            Text(product.price)
                .font(.custom(customFont, size: 16))
                .fontWeight(.bold)
                .foregroundColor(Color("Purple"))
                .padding(.top, 5)
        }
        .padding(.horizontal,20)
        .padding(.bottom, 22)
        .background(
            Color.white.cornerRadius(25)
            )
    }
    
}

有人知道问题出在哪里吗?

因此,只有在选择类别时,才会显示我的项目。

Watch here the issue

getData() 中的操作是异步的。这意味着该函数内部发生的事情不一定会在您在 init 方法中调用 filteredProductByType() 时执行。因此,在第一个 运行 上,它几乎可以保证 filteredProducts 为空。

要解决此问题,例如,您可以在 getData.

中的 self.products = documents.map { 闭包之后调用 filteredProductByType

话虽如此,处理问题的更有效方法可能是将 filteredProducts 转换为 计算的 属性 以生成其结果被调用时。类似于:

var filteredProducts: [Product] {
  products.filter { [=10=].type == self.productType }
}

然后,您再也不必调用 filteredProductByType 并担心它发生的顺序。