Swift开发:如何在Firebase用户文档存储关联Box集合数据
Firestore 存储用户关联推进Box的实现方案
核心设计思路
Box 数据本身存在独立集合,用户文档不需要存储完整 Box 副本,只存储对应 Box 的文档 ID 做关联即可。这种方式可以避免数据冗余,后续 Box 信息更新时不需要同步修改所有关联用户的文档,是 Firestore 关联数据的标准实践。你之前定义的 WorkingOnBoxes 结构体属于多余封装,可以直接删除。
第一步:调整数据模型适配 Codable 序列化
Firebase 官方提供的 Swift SDK 原生支持 Codable 协议做数据序列化/反序列化,不需要手动拼接字典,所有需要和 Firestore 交互的模型都要遵循 Codable 协议。
修改后的 Box 模型
本地 UI 状态(比如 isWorkingOn)不需要存入 Firestore,通过 CodingKeys 排除即可:
import FirebaseFirestoreSwift import FirebaseAuth struct Box: Identifiable, Codable { // 映射 Firestore 文档ID,自动和文档ID绑定 @DocumentID var id: String? var box_name: String var box_type: String var box_profileImage: String // 本地UI状态,不参与Firestore序列化 var isWorkingOn: Bool = false enum CodingKeys: String, CodingKey { case id, box_name, box_type, box_profileImage } }
修改后的 UserModel
新增 activelyWorkingTowardsBoxes 字段,类型为字符串数组,存储最多2个 Box 的文档ID:
struct UserModel: Identifiable, Codable { @DocumentID var id: String? var username: String var pic: String var bio: String var uid: String // 存储正在推进的Box文档ID,默认空数组 var activelyWorkingTowardsBoxes: [String] = [] }
第二步:改造交互逻辑,同步本地状态与Firestore数据
首先在你的全局状态/视图模型中初始化 Firestore 实例:private let db = Firestore.firestore()
新增同步方法,本地状态变更后自动写入Firestore
改造你之前的 addToWorkingOn 方法,加入2个Box的数量限制,操作完本地数组后立刻同步到云端:
// 同步当前选中的Box到Firestore用户文档 private func syncWorkingBoxesToFirestore() { // 校验当前用户登录状态 guard let currentUid = Auth.auth().currentUser?.uid else { return } // 提取Box的文档ID,过滤空值 let boxIdList = sharedData.workingOnBoxes.compactMap { $0.id } do { // merge: true 表示只更新传入的字段,不会覆盖用户名下、头像、简介等其他已有数据 try db.collection("users").document(currentUid).setData( ["activelyWorkingTowardsBoxes": boxIdList], merge: true ) } catch { print("同步推进Box数据失败: \(error.localizedDescription)") } } // 点击WorkTowards按钮触发的方法 func toggleWorkingStatus(for box: Box) { if let index = sharedData.workingOnBoxes.firstIndex(where: { $0.id == box.id }) { // 已在推进列表则移除 sharedData.workingOnBoxes.remove(at: index) } else { // 不在列表则先校验数量上限 guard sharedData.workingOnBoxes.count < 2 else { // 此处可触发UI提示,告知用户最多同时推进2个Box return } sharedData.workingOnBoxes.append(box) } // 同步变更到云端 syncWorkingBoxesToFirestore() } // 判断Box是否在推进列表的方法 func isBoxInWorkingList(_ box: Box) -> Bool { sharedData.workingOnBoxes.contains(where: { $0.id == box.id }) }
第三步:拉取用户数据时解析关联Box
App启动/用户登录后拉取用户数据时,先读取用户文档中存储的Box ID数组,再批量查询对应的Box完整数据,赋值给本地全局状态即可:
func loadCurrentUserData() { guard let currentUid = Auth.auth().currentUser?.uid else { return } db.collection("users").document(currentUid).getDocument { [weak self] snapshot, error in guard let self = self, error == nil, let snapshot = snapshot else { return } do { let userInfo = try snapshot.data(as: UserModel.self) let workingBoxIds = userInfo.activelyWorkingTowardsBoxes // 没有选中的Box直接清空本地列表 guard !workingBoxIds.isEmpty else { DispatchQueue.main.async { self.sharedData.workingOnBoxes = [] } return } // 批量查询ID对应的Box文档,in查询最多支持10个元素,完全满足2个的需求 self.db.collection("boxes") .whereField(FieldPath.documentID(), in: workingBoxIds) .getDocuments { boxSnapshot, boxError in guard boxError == nil, let boxDocs = boxSnapshot?.documents else { return } do { let workingBoxes = try boxDocs.compactMap { try $0.data(as: Box.self) } DispatchQueue.main.async { self.sharedData.workingOnBoxes = workingBoxes } } catch { print("解析Box数据失败: \(error.localizedDescription)") } } } catch { print("解析用户数据失败: \(error.localizedDescription)") } } }
注意事项
- 写入用户文档时必须设置
merge: true,否则会覆盖文档下所有已有字段,导致用户名、头像等数据丢失 - 建议在Firestore安全规则中再加一层数量校验,避免客户端被篡改后绕过2个Box的限制,规则示例:
match /users/{userId} { allow write: if request.auth.uid == userId && request.resource.data.activelyWorkingTowardsBoxes.size() <= 2; }
- Box的
isWorkingOn属于纯本地UI状态,不需要持久化存储,每次拉取完workingOnBoxes列表后,遍历全量Box列表判断ID是否存在即可动态赋值,避免多端状态不同步。
内容的提问来源于stack exchange,提问作者Mxyb
相关产品推荐
相关产品推荐

