Swift中通过Firestore更新视图变量时遇错误,寻求解决方案
解决SwiftUI中Firestore自动更新currentRating的问题
先梳理你现有代码里的核心问题:
- FirestoreManager里的
fetchRatingSubmission用了document(),这会生成随机文档ID,根本拿不到对应学校的评分数据;而且currentRating没加@Published,SwiftUI监听不到它的变化。 - NewRatingView里直接用
var model = FirestoreManager(),无法监听ObservableObject的更新,得用@StateObject(iOS14及以上)或者@ObservedObject。 - 你要实现提交评分后自动更新平均值,得先有提交评分并计算新平均值写入Firestore的逻辑,再让Firestore实时同步到本地。
下面是修正后的完整代码:
1. 修正FirestoreManager类
import FirebaseFirestore import SwiftUI class FirestoreManager: ObservableObject { // 用@Published让SwiftUI监听变化 @Published var currentRating: CGFloat = 0.0 @Published var totalRatings: Float = 0.0 @Published var usualRating: Float = 0.0 private var listener: ListenerRegistration? // 监听指定学校的评分实时变化 func listenToSchoolRating(for location: String) { let db = Firestore.firestore() let docRef = db.collection("RatingInformation").document(location) // 移除之前的监听,避免内存泄漏 listener?.remove() // 添加快照监听,实时获取更新 listener = docRef.addSnapshotListener { [weak self] document, error in guard let self = self, let document = document, document.exists else { print("获取评分数据失败或文档不存在") return } self.currentRating = CGFloat(document.data()?["currentRating"] as? Float ?? 0) self.totalRatings = document.data()?["totalRatings"] as? Float ?? 0 self.usualRating = document.data()?["usualRating"] as? Float ?? 0 } } // 提交新评分,计算并更新Firestore中的平均值 func submitNewRating(for location: String, newRating: Float) { let db = Firestore.firestore() let docRef = db.collection("RatingInformation").document(location) // 先获取当前数据,计算新平均值 docRef.getDocument { document, error in guard let document = document, document.exists else { // 如果是第一次评分,直接初始化数据 let initialData: [String: Any] = [ "currentRating": newRating, "totalRatings": 1, "usualRating": newRating, "location": location ] docRef.setData(initialData) return } let currentTotal = document.data()?["totalRatings"] as? Float ?? 0 let currentAvg = document.data()?["currentRating"] as? Float ?? 0 // 计算新的平均值:(当前总和 + 新评分) / (总次数 + 1) let newTotal = currentTotal + 1 let newAvg = (currentAvg * currentTotal + newRating) / newTotal // 更新Firestore数据 let updatedData: [String: Any] = [ "currentRating": newAvg, "totalRatings": newTotal // usualRating如果需要更新也可以在这里处理 ] docRef.updateData(updatedData) } } // 销毁时移除监听 deinit { listener?.remove() } }
2. 修正RatingSubmission结构(可选,如需用数组存储)
struct RatingSubmission: Identifiable { var id: String var location: String var currentRating: Float var usualRating: Float var totalRatings: Float // 从Firestore文档初始化 init(document: DocumentSnapshot) { self.id = document.documentID self.location = document.data()?["location"] as? String ?? "" self.currentRating = document.data()?["currentRating"] as? Float ?? 0 self.usualRating = document.data()?["usualRating"] as? Float ?? 0 self.totalRatings = document.data()?["totalRatings"] as? Float ?? 0 } }
3. 修正NewRatingView视图
struct NewRatingView: View { // 用@StateObject管理FirestoreManager,确保生命周期和视图绑定 @StateObject private var manager = FirestoreManager() let schools = ["North Avenue", "West Village", "Brittain"] @State private var selectedSchool = "West Village" @State private var newRating: Float = 3.0 // 假设评分范围1-5 var body: some View { VStack(spacing: 20) { // 显示当前选中学校的评分 Text("\(selectedSchool) 当前评分: \(manager.currentRating, specifier: "%.1f")") .font(.title) // 选择学校 Picker("选择学校", selection: $selectedSchool) { ForEach(schools, id: \.self) { school in Text(school) } } .pickerStyle(.segmented) .onChange(of: selectedSchool) { newSchool in // 切换学校时监听对应评分 manager.listenToSchoolRating(for: newSchool) } // 评分输入示例(可替换为自定义评分组件) Slider(value: $newRating, in: 1...5, step: 0.5) Text("你的评分: \(newRating, specifier: "%.1f")") // 提交按钮 Button("提交评分") { manager.submitNewRating(for: selectedSchool, newRating: newRating) // 提交后重置评分输入 newRating = 3.0 } .buttonStyle(.borderedProminent) } .padding() .onAppear { // 视图出现时监听默认学校的评分 manager.listenToSchoolRating(for: selectedSchool) } } }
关键说明
- 用
addSnapshotListener替代getDocument,实现Firestore数据的实时同步,只要Firestore里的评分更新,本地的currentRating会自动刷新,SwiftUI视图也会跟着更新。 @StateObject用来管理FirestoreManager实例,确保视图能监听它的@Published属性变化。- 提交评分时,先获取当前的总评分次数和平均值,计算出新的平均值后再更新Firestore,这样每次提交后,Firestore的数据变化会通过监听同步回本地。
内容的提问来源于stack exchange,提问作者FrancescaD
相关产品推荐
相关产品推荐

