如何将用户的 Firestore 映射读取到 Swift 词典?

How do I read a User's Firestore Map to a Swift Dictionary?

我有我的用户结构,其中包含他们所有社交媒体的字典。

struct User: Identifiable {

var id: String { uid }

let uid, email, name, bio, profileImageUrl: String
let numSocials, followers, following: Int

var socials: [String: String]


init(data: [String: Any]) {
    self.uid = data["uid"] as? String ?? ""
    self.email = data["email"] as? String ?? ""
    self.name = data["name"] as? String ?? ""
    self.bio = data["bio"] as? String ?? ""
    self.profileImageUrl = data["profileImageURL"] as? String ?? ""
    
    self.numSocials = data["numsocials"] as? Int ?? 0
    self.followers = data["followers"] as? Int ?? 0
    self.following = data["following"] as? Int ?? 0
    
    self.socials = data["socials"] as? [String: String] ?? [:]

}
}

socials(字典)的想法是动态的,因为用户可以添加和删除社交媒体。 Firestore 看起来像这样:

字典初始化为空。我已经能够使用此功能将元素添加到字典中:

private func addToStorage(selectedMedia: String, username: String) -> Bool {
    if username == "" {
        return false
    }
    guard let uid = FirebaseManager.shared.auth.currentUser?.uid else {
        print("couldnt get uid")
        return false
    }
    
    FirebaseManager.shared.firestore.collection("users").document(uid).setData([ "socials": [selectedMedia:username] ], merge: true)

    print("yoo")
    return true
}

但是我似乎无法将 firestore 映射读入我的 swiftui 字典。 我想这样做以便我可以执行 ForEach 循环并列出所有映射。如果地图是空的,那么列表也会是空的,但我想不通。

以防万一,这是我的视图模型。

class MainViewModel: ObservableObject {

@Published var errorMessage = ""
@Published var user: User?

init() {
    DispatchQueue.main.async {
        self.isUserCurrentlyLoggedOut = FirebaseManager.shared.auth.currentUser?.uid == nil
    }

    fetchCurrentUser()
    
}

func fetchCurrentUser() {
    guard let uid = FirebaseManager.shared.auth.currentUser?.uid else {
        self.errorMessage = "Could not find firebase uid"
        print("FAILED TO FIND UID")
        return

    }

    FirebaseManager.shared.firestore.collection("users").document(uid).getDocument { snapshot, error in
        if let error = error {
            self.errorMessage =  "failed to fetch current user: \(error)"

            print("failed to fetch current user: \(error)")
            return
        }
        guard let data = snapshot?.data() else {
            print("no data found")
            self.errorMessage = "No data found"
            return

        }


        self.user = .init(data: data)

    }
}
   
}

TLDR: 我不知道如何将我的 firestore 地图作为 swiftui 字典。每当我尝试访问我的用户词典时,都会出现以下错误。如果我强制解包它会在运行时崩溃。我试图合并“??”但是我不知道如何让它成为它想要的类型。

ForEach(vm.user?.socials.sorted(by: >) ?? [String:String], id: \.key) { key, value in
                    linkDisplay(social: key, handler: value)
                        .listRowSeparator(.hidden)


                }.onDelete(perform: delete)

error to figure out

请耐心等待。很长一段时间以来,我一直在通过 SO 和其他地方寻找答案。这对我来说都是新的。提前致谢。

这是一个分为两部分的答案;第 1 部分通过一组已知的社交网站(Github、Pinterest 等)解决了这个问题。我将其包括在内是为了展示如何将 Map 映射到 Codable。

第 2 部分是答案(长话短说,跳到第 2 部分),因此可以将社交映射到不同社交的字典。

第 1 部分:

这是一个将 Firestore 数据映射到可编码对象(包括社交地图字段)的缩写结构。它特定于列出的 4 个社会领域。

struct SocialsCodable: Codable {
    var Github: String
    var Pinterest: String
    var Soundcloud: String
    var TikTok: String
}

struct UserWithMapCodable: Identifiable, Codable {
    @DocumentID var id: String?
    var socials: SocialsCodable? //socials is a `map` in Firestore
}

以及读取该数据的代码

func readCodableUserWithMap() {
    let docRef = self.db.collection("users").document("uid_0")

    docRef.getDocument { (document, error) in
        if let err = error {
            print(err.localizedDescription)
            return
        }

        if let doc = document {
            let user = try! doc.data(as: UserWithMapCodable.self)
            print(user.socials) //the 4 socials from the SocialsCodable object
        }
    }
}

第 2 部分:

这是将 socials 地图字段视为字典的答案

struct UserWithMapCodable: Identifiable, Codable {
    @DocumentID var id: String?
    var socials: [String: String]?
}

然后是将 Firestore 数据映射到对象的代码

func readCodableUserWithMap() {
    let docRef = self.db.collection("users").document("uid_0")

    docRef.getDocument { (document, error) in
        if let err = error {
            print(err.localizedDescription)
            return
        }

        if let doc = document {
            let user = try! doc.data(as: UserWithMapCodable.self)
            if let mappedField = user.socials {
                mappedField.forEach { print([=13=].key, [=13=].value) }
            }
        }
    }
}

和第 2 部分的输出

TikTok ogotok
Pinterest pintepogo
Github popgit
Soundcloud musssiiiccc

我可能还建议将社交完全从用户文档中取出并将其存储为一个单独的集合

socials
   some_uid
      Github: popgit
      Pinterest: pintepogo
   another_uid
      Github: git-er-done
      TikTok: dancezone

这非常可扩展,并允许进行一些很酷的查询:例如哪些用户拥有 TikTok。