Swift中Firestore数组内对象的selectedCards字段更新问题
我将Game对象存储在Firestore数据库中,当向selectedCards数组添加Card对象时,不想更新整个Game对象,仅需更新当前Player的selectedCards数组。
数据结构代码
struct Game: Codable { let id: String var table = Table() var players = [Player]() var player1ID = "" var player2ID = "" var currentPlayerSetCountShouldAnimate = false var opponentSetCountShouldAnimate = false init(currentPlayer: Player) { id = UUID().uuidString players.append(currentPlayer) players[0].playerNumber = 0 player1ID = currentPlayer.id } } struct Player: Codable, Equatable { var id = UUID().uuidString var playerNumber = 0 var selectedCards: [Card] var collectedSets: [Card] var selectedCardsIndexes: [Int] }
更新函数代码
func updatePlayerSelectedCards(player: Player) { do { let selectedCards = try JSONEncoder().encode(player.selectedCards) let gameRef = Firestore.firestore().collection("Game").document(game.id) let playerRef = gameRef.collection("players") playerRef.document("\(player.playerNumber)").updateData(["selectedCards": selectedCards]) { (error) in if let error = error { print("Error updating selectedCards field: \(error)") } else { print("Successfully updated selectedCards field") } } } catch { print(error.localizedDescription) } }
遇到的错误
[FirebaseFirestore][I-FST000001] WriteStream (7f9b27810c08) Stream error: 'Not found: No document to update: projects/setgamebc/databases/(default)/documents/Game/FC600D8C-85DC-4465-8648-31FFA90086EB/players/0'
我从附件图片可见players数组存在索引0,但疑惑该索引并非对应文档?是否需用FieldValue.arrayUnion?这是我的首个大型项目,希望得到帮助与解答。
1. 核心问题分析
你的代码存在存储结构逻辑矛盾:
- 初始化Game时,Player被存入Game结构体的
players数组,意味着在Firestore中,Player是Game文档的数组字段。 - 但更新函数里,你试图访问Game文档下的
players子集合,查找对应playerNumber的文档——而你从未创建过这些子集合文档,因此会触发“找不到文档”的错误。
2. 两种可行修正方案
方案一:直接更新Game文档的嵌套数组字段(推荐)
如果Player本就是Game的一部分,无需使用子集合,直接通过点路径语法定位到对应Player的selectedCards字段进行更新即可:
func updatePlayerSelectedCards(player: Player) { do { // 使用Firestore专用编码器适配数据格式 let selectedCardsData = try Firestore.Encoder().encode(player.selectedCards) let gameRef = Firestore.firestore().collection("Game").document(game.id) // 用点路径定位到players数组指定索引的selectedCards字段 gameRef.updateData([ "players.\(player.playerNumber).selectedCards": selectedCardsData ]) { error in if let error = error { print("更新失败:\(error)") } else { print("selectedCards更新成功") } } } catch { print("编码失败:\(error.localizedDescription)") } }
方案二:改用子集合存储Player(适合Player数据量大的场景)
若想将Player存入Game的子集合,需在初始化Game时同步创建对应Player文档,而非仅存入Game的数组:
init(currentPlayer: Player) { id = UUID().uuidString players.append(currentPlayer) players[0].playerNumber = 0 player1ID = currentPlayer.id // 同步创建子集合中的Player文档 let gameRef = Firestore.firestore().collection("Game").document(id) do { let playerData = try Firestore.Encoder().encode(currentPlayer) gameRef.collection("players").document("\(currentPlayer.playerNumber)").setData(playerData) } catch { print("初始化Player文档失败:\(error)") } }
完成初始化后,你的原更新函数即可正常使用(建议替换JSONEncoder为Firestore.Encoder)。
3. 关于FieldValue.arrayUnion的使用
若你的需求是往selectedCards数组追加元素,而非替换整个数组,使用FieldValue.arrayUnion会更高效,还能避免客户端与服务端数组状态不一致的问题:
func addCardToSelectedCards(player: Player, newCard: Card) { do { let cardData = try Firestore.Encoder().encode(newCard) let gameRef = Firestore.firestore().collection("Game").document(game.id) gameRef.updateData([ "players.\(player.playerNumber).selectedCards": FieldValue.arrayUnion([cardData]) ]) { error in if let error = error { print("添加卡片失败:\(error)") } else { print("卡片成功加入selectedCards") } } } catch { print("编码失败:\(error.localizedDescription)") } }
内容的提问来源于stack exchange,提问作者brad8989

