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

SwiftUI中@FirestoreQuery权限问题及数据实时更新咨询

SwiftUI中Firebase实时监听失效的解决办法

初始场景与权限问题

最初使用@FirestoreQuery从Firebase获取项目列表,安全规则开放时(允许所有登录用户读写)一切正常:

match /databases/{database}/documents {
  match /projects/{projectName=**} {
    allow read, write: if request.auth != null;
  }
}

但修改为仅允许文档所有者读写后:

match /databases/{database}/documents {
  match /projects/{projectName} {
    allow read, update, delete: if request.auth != null && resource.data.owner == request.auth.uid;
    allow create: if request.auth != null;
  }
}

出现权限错误:[FirebaseFirestore][I-FST000001] Listen for query at projects failed: Missing or insufficient permissions。

需要给查询添加owner等于当前用户ID的过滤条件,但直接在@FirestoreQuery的collectionPath中用字符串插值无效,因为它仅接受静态路径字符串。

相关代码

Project Model

import Foundation
import Firebase
import FirebaseFirestoreSwift

struct Project: Identifiable, Codable, Hashable {
    @DocumentID var id: String?
    var projectName = ""
    var createdOn = Date()
    var createdBy = ""
    var projectColor = ""
    var owner = ""
    
    var dictionary: [String: Any] {
        return ["projectName": projectName, 
                "createdOn": Timestamp(date: Date()), 
                "createdBy": Auth.auth().currentUser?.email ?? "", 
                "projectColor": projectColor, 
                "owner": Auth.auth().currentUser?.uid ?? ""]
    }
}

初始视图代码

struct MainView: View {
    @FirestoreQuery(collectionPath: "projects", predicates: [.orderBy("createdOn", true)] ) var projects: [Project]
    
    @Environment(\.dismiss) private var dismiss
    @EnvironmentObject var mainViewVM: MainViewModel
    @State private var settingsViewSheetIsPresented = false
    @State private var createProjectViewSheetIsPresented = false
    @State private var searchTerm = ""
    
    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            NavigationStack {
                List {
                    ForEach(projects) { project in
                        HStack {
                            Rectangle()
                                .fill(Color("\(project.projectColor)"))
                                .frame(width:10)
                            VStack(alignment: .leading, spacing: 5) {
                                HStack {
                                    NavigationLink {
                                        ProjectView(project: project)
                                            .environmentObject(TeamViewModel())
                                    } label: {
                                        Text(project.projectName)
                                            .font(.title2.bold())
                                            .lineLimit(1)
                                            .foregroundStyle(Color("\(project.projectColor)"))
                                    }
                                }
                                HStack {
                                    Text("Created \(project.createdOn.formatted(date:.abbreviated, time: .shortened)), by \(project.createdBy)")
                                        .foregroundStyle(.secondary)
                                        .font(.caption2)
                                        .lineLimit(1)
                                        .minimumScaleFactor(0.5)
                                }
                            }
                        }
                    }
                }
                // ... 其余视图代码
            }
        }
    }
}

尝试的解决方案与新问题

改用ViewModel手动实现查询,添加了owner和accessList的过滤条件,配合更新后的安全规则可以正常获取数据:

更新后的安全规则

match /projects/{projectName=**} {
   allow read, update, delete: if !('owner' in resource.data) || request.auth.uid == resource.data.owner;
   allow read, update, delete: if request.auth.token.email_verified in resource.data.accessList || request.auth.uid == resource.data.owner;
   allow create: if request.auth.uid != null;
}

ViewModel代码

class MainViewModel: ObservableObject {
    @Published var projects: [Project] = []
    private var db = Firestore.firestore()

    init() {
        loadProjects()
    }
    func loadProjects() {
        guard let userId = Auth.auth().currentUser?.uid, let userEmail = Auth.auth().currentUser?.email else 
        { return }
        
        db.collection("projects").whereFilter(Filter.orFilter([
            Filter.whereField("owner", isEqualTo: userId),
            Filter.whereField("accessList", arrayContains: userEmail)
        ]))
            .order(by: "createdOn", descending: true)
            .addSnapshotListener { (querySnapshot, error) in
                if let querySnapshot = querySnapshot {
                    self.projects = querySnapshot.documents.compactMap { document -> Project? in
                        try? document.data(as: Project.self)
                    }
                }
        }
    }
}

但出现新问题:文档更新或添加时无法触发实时响应。


解决实时监听失效的方案

1. 保留监听引用,避免被自动释放

Firestore的addSnapshotListener返回ListenerRegistration对象,如果不保留该引用,系统会自动释放监听,导致无法持续接收更新。修改ViewModel,添加监听引用并在销毁时移除。

2. 切换到主队列更新UI数据

Firestore的快照回调在后台线程执行,直接更新@Published属性可能无法触发UI刷新,需要手动切换到主队列。

修改后的ViewModel代码

class MainViewModel: ObservableObject {
    @Published var projects: [Project] = []
    private var db = Firestore.firestore()
    private var listenerRegistration: ListenerRegistration? // 保存监听引用

    init() {
        loadProjects()
    }
    
    func loadProjects() {
        // 先移除旧监听,避免重复监听
        listenerRegistration?.remove()
        
        guard let userId = Auth.auth().currentUser?.uid, let userEmail = Auth.auth().currentUser?.email else { return }
        
        // 保存监听引用
        listenerRegistration = db.collection("projects")
            .whereFilter(Filter.orFilter([
                Filter.whereField("owner", isEqualTo: userId),
                Filter.whereField("accessList", arrayContains: userEmail)
            ]))
            .order(by: "createdOn", descending: true)
            .addSnapshotListener { [weak self] (querySnapshot, error) in
                guard let self = self else { return }
                
                if let error = error {
                    print("监听失败:\(error.localizedDescription)")
                    return
                }
                
                guard let querySnapshot = querySnapshot else { return }
                
                // 切换到主队列更新UI数据
                DispatchQueue.main.async {
                    self.projects = querySnapshot.documents.compactMap { document -> Project? in
                        try? document.data(as: Project.self)
                    }
                }
            }
    }
    
    // 销毁ViewModel时移除监听
    deinit {
        listenerRegistration?.remove()
    }
}

3. 验证查询条件匹配

确保新增/修改的文档满足以下任一条件,否则不会被查询到,也就不会触发监听更新:

  • owner字段等于当前用户的UID
  • accessList数组包含当前用户的已验证邮箱

补充:@FirestoreQuery的正确用法(可选)

如果想回到@FirestoreQuery,可以通过初始化时传入动态predicates实现过滤:

struct MainView: View {
    @State private var userId: String? = Auth.auth().currentUser?.uid
    @FirestoreQuery var projects: [Project]
    
    // 初始化时设置predicates
    init() {
        let predicates: [FirestoreQueryPredicate] = [
            .whereField("owner", isEqualTo: userId ?? ""),
            .orderBy("createdOn", descending: true)
        ]
        _projects = FirestoreQuery(collectionPath: "projects", predicates: predicates)
    }
    
    // 监听用户ID变化,重新初始化查询
    var body: some View {
        // ... 视图内容
        .onChange(of: userId) { newUserId in
            let predicates: [FirestoreQueryPredicate] = [
                .whereField("owner", isEqualTo: newUserId ?? ""),
                .orderBy("createdOn", descending: true)
            ]
            _projects = FirestoreQuery(collectionPath: "projects", predicates: predicates)
        }
    }
}

内容的提问来源于stack exchange,提问作者kuba

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 23:54:56