Firestore监听器触发后UI无法更新问题求助
问题:Firestore文档更新时二级视图UI无法同步更新
我需要在Firestore文档变化时更新UI,控制台已确认每次文档变更都会触发监听器。监听器和readyOrders均使用@Published修饰。
监听器代码
func getReadyOrders() { referance .collection(path) .document(email) .collection("CompletedOrders") .whereField("placedBy", isEqualTo: user) .addSnapshotListener { orderSnapshot, error in DispatchQueue.main.async { guard let snapshot = orderSnapshot?.documents else{ print("There is no active orders") return } self.readyOrders = snapshot.map{ activeSnapshot -> ActiveOrder in let data = activeSnapshot.data() var collectedItems = [MenuItem]() var collectedDrinks = [DrinkItem]() let id = activeSnapshot.documentID let placed = data["placedBy"] as? String ?? "" let inZone = data["inZone"] as? String ?? "" let forTable = data["forTable"] as? String ?? "" let orderItems = data["orderItems"] as? [String] let orderDrinks = data["orderDrinks"] as? [String] let orderItemsReady = data["orderItemsReady"] as? Bool ?? false let orderDrinksReady = data["orderDrinksReady"] as? Bool ?? false let totalAmount = data["totalAmount"] as? Double ?? 0.00 orderItems?.forEach({ item in let parts = item.components(separatedBy: "/") collectedItems.append(MenuItem(itemName: parts[0], price: Double(parts[1]))) }) orderDrinks?.forEach({ drink in let itemPart = drink.components(separatedBy: "/") collectedDrinks.append(DrinkItem(drinkName: itemPart[0], price: Double(itemPart[1]))) }) return ActiveOrder(id: id, placedBy: placed, inZone: inZone, forTable: forTable, orderItems: collectedItems, orderDrinks: collectedDrinks, orderItemsReady: orderItemsReady, orderDrinksReady: orderDrinksReady, totalAmount: totalAmount) } } } }
一级视图(可正常更新)
一级视图展示所有文档,新增或删除文档时UI能正常更新:
Section { ForEach(handler.readyOrders, id: \.id){ readyOrder in NavigationLink{ OrderComplete(handler: handler, order: readyOrder, currency: currency) } label: { HStack{ Text(readyOrder.inZone!) Text("- \(readyOrder.forTable!)") } } } } header: { Text("Order's ready:") }
二级视图(无法更新)
二级视图接收ForEach传递的readyOrder,通过内部ForEach展示订单内的数组,但文档内容变更时UI无法更新:
ForEach(order.orderItems!, id:\.id){ item in HStack{ Text(item.itemName!) Spacer() Text(currency.format(item.price!)) .foregroundColor(.teal) Image(systemName: "arrow.left") .foregroundColor(.red) } } .onDelete(perform: deleteItem)
最小复现代码
模型代码
struct Order: Identifiable{ var id = UUID().uuidString var placedBy: String? var inZone: String? var forTable: String? var orderItems: [MenuItem]? var orderDrinks: [DrinkItem]? var orderItemsReady: Bool? var orderDrinksReady: Bool? var totalAmount: Double? }
ViewModel代码
class ViewModel: ObservableObject{ @Published var orders = [Order]() private var referance = Firestore.firestore() func getReadyOrders() { referance .collection(path) .document(email) .collection("CompletedOrders") .whereField("placedBy", isEqualTo: user) .addSnapshotListener { orderSnapshot, error in DispatchQueue.main.async { guard let snapshot = orderSnapshot?.documents else{ print("There is no active orders") return } self.readyOrders = snapshot.map{ activeSnapshot -> ActiveOrder in let data = activeSnapshot.data() var collectedItems = [MenuItem]() var collectedDrinks = [DrinkItem]() let id = activeSnapshot.documentID let placed = data["placedBy"] as? String ?? "" let inZone = data["inZone"] as? String ?? "" let forTable = data["forTable"] as? String ?? "" let orderItems = data["orderItems"] as? [String] let orderDrinks = data["orderDrinks"] as? [String] let orderItemsReady = data["orderItemsReady"] as? Bool ?? false let orderDrinksReady = data["orderDrinksReady"] as? Bool ?? false let totalAmount = data["totalAmount"] as? Double ?? 0.00 orderItems?.forEach({ item in let parts = item.components(separatedBy: "/") collectedItems.append(MenuItem(itemName: parts[0], price: Double(parts[1]))) }) orderDrinks?.forEach({ drink in let itemPart = drink.components(separatedBy: "/") collectedDrinks.append(DrinkItem(drinkName: itemPart[0], price: Double(itemPart[1]))) }) return ActiveOrder(id: id, placedBy: placed, inZone: inZone, forTable: forTable, orderItems: collectedItems, orderDrinks: collectedDrinks, orderItemsReady: orderItemsReady, orderDrinksReady: orderDrinksReady, totalAmount: totalAmount) } } } } func delteItem(menuItem: MenuItem, from order: ActiveOrder){ let item = menuItem.itemName! + "/" + String(menuItem.price!) let pathTo = referance.collection(path).document(email).collection("CompletedOrders").document(order.id) pathTo.getDocument { snapshot, error in if let document = snapshot, document.exists{ var items = document.data()!["orderItems"] as? [String] ?? [] let index = items.firstIndex(where: { $0 == item }) items.remove(at: index!) pathTo.updateData(["orderItems" : items]){ error in if let _ = error{ print("Error deleting and updating order array") } } } } } }
视图代码
struct View1: View{ @ObservedObject var viewModel: ViewModel var body: some View{ VStack{ ForEach(viewModel.orders, id: \.id){ readyOrder in NavigationLink{ View2(viewModel: viewModel, order: readyOrder) } label: { HStack{ Text(readyOrder.inZone!) Text("- \(readyOrder.forTable!)") } } } } } } struct View2: View{ @ObservedObject var viewModel: ViewModel var order: Order func deleteItem(at offest: IndexSet){ let index = offest[offest.startIndex] let deleteItem = order.orderItems![index] handler.delteItem(menuItem: deleteItem, from: order) } // 希望在此视图中展示文档更新后的内容(包括元素被移除的情况) var body: some View{ VStack{ ForEach(order.orderItems!, id:\.id){ item in HStack{ Text(item.itemName!) Spacer() Text(item.price!) .foregroundColor(.teal) Image(systemName: "arrow.left") .foregroundColor(.red) } } .onDelete(perform: deleteItem) } } }
解决方案
问题核心是二级视图持有了order值类型的旧副本,ViewModel的orders数组更新时,二级视图不会自动同步最新数据。
修复步骤
- 二级视图不再直接接收
order参数,改为接收订单id - 通过ViewModel的
orders数组,根据id实时获取最新订单数据
修改后的View2代码:
struct View2: View{ @ObservedObject var viewModel: ViewModel let orderId: String // 改为接收订单ID // 通过ID获取最新订单 private var currentOrder: Order? { viewModel.orders.first(where: { $0.id == orderId }) } func deleteItem(at offest: IndexSet){ guard let order = currentOrder, let deleteItem = order.orderItems?[offest[offest.startIndex]] else { return } viewModel.delteItem(menuItem: deleteItem, from: order) } var body: some View{ VStack{ if let order = currentOrder, let items = order.orderItems { ForEach(items, id:\.id){ item in HStack{ Text(item.itemName!) Spacer() Text("\(item.price!)") .foregroundColor(.teal) Image(systemName: "arrow.left") .foregroundColor(.red) } } .onDelete(perform: deleteItem) } } } }
同时修改View1中的NavigationLink参数传递:
NavigationLink{ View2(viewModel: viewModel, orderId: readyOrder.id) } label: { HStack{ Text(readyOrder.inZone!) Text("- \(readyOrder.forTable!)") } }
额外优化建议
- 模型中的可选值尽量改为非可选(根据业务场景),减少强制解包风险
MenuItem和DrinkItem需遵循Identifiable协议,确保id唯一性- 修正ViewModel中
delteItem的拼写错误为deleteItem
内容的提问来源于stack exchange,提问作者Ivars
相关产品推荐
相关产品推荐

