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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 04:03:35