You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 01:50:25