【问题标题】:ForEach on a dictionary字典上的 ForEach
【发布时间】:2022-11-15 18:11:11
【问题描述】:

我需要我的应用程序来显示数据表。数据看起来像["body": Optional("Go Shopping"), "isDeleted": Optional(false), "_id": Optional("63333b1600ce507b0097e3b3"), "isCompleted": Optional(false)] 表的列标题将是键body, isDeleted, isCompleted, _id。我将拥有该数据的多个实例,它们具有相同的键,但值不同。我需要在各自的标题下显示每个数据实例的值,并且每一行都属于一个数据实例。

例子:

我很挣扎,因为我能想到的唯一方法是使用字典,但是在视图中使用字典时遇到了很多问题。

*** 重要的提示: 该应用程序允许用户选择某个集合,然后该应用程序将加载该集合的所有数据。每个集合在其数据中都有不同的键,因此我无法创建特定的结构,因为我实际上并不知道数据中的键/值。从某种意义上说,模型必须是动态的,因为我不知道每个集合中将使用哪些键/值类型,并且在选择不同的集合时需要重绘表格。

我试过的

一个包含“值:[String: Any?]”的文档类,字符串是键,Any 是数据实例的值

class Document{
    let value: [String:Any?]
    
    init(value:[String:Any?]) {
        self.value = value
    }
}

在我的 ViewModel 中,我调用了一个数据库,该数据库使用所选集合名称返回该集合中所有文档的数组。我遍历数组并创建一个 Document obj,其 Document 的值看起来像 ["body": Optional("Go Shopping"), "isDeleted": Optional(false), "_id": Optional("63333b1600ce507b0097e3b3"), "isCompleted": Optional(false)],然后我将每个 Document 添加到 Document 的数组中

class DocumentsViewModel : ObservableObject {
    @Published var docKeys: [String]?
    @Published var docsList: [Document]?

    func getDocs() {
       ... //Database call to get docs from collection

            for doc in docs {
                // add doc keys to array (used for table header)
                self.docKeys = doc.value.keys.map{$0}
                
                self.docsList?.append(Document(value: doc.value))
    }

然后在我的视图中,我尝试首先显示来自 docKeys 的标题,然后使用该键遍历 [Document] 数组并访问值 var 并使用该键获取正确的值以显示在该文档的标题下

    var body: some View {
        Text(viewModel.collectionName)
        
        HStack {
            ForEach(viewModel.docKeys ?? [], id: \.description) {key in
                Text(key.name)
                VStack {
                    ForEach(viewModel.docsList ?? [], id: \.value) { doc in
                        Text(doc.value[property.name])
                    }
                }
            }
        }
    }

经过研究,我明白为什么我不能在未排序的字典上使用 ForEach。

我将接受有关如何显示此表的任何帮助/指导。另外,除了使用字典之外,是否还有其他建议?谢谢你!

【问题讨论】:

  • 您是否希望 Document 值属性中的数据只是 String 和 Bool,或者可能包括 Int、Double、数组、自定义类型等...?

标签: ios dictionary object swiftui foreach


【解决方案1】:

只是一些提示:

  • 如果您需要“带顺序的字典”,您可以尝试使用Key-ValuePairs 对象,它本质上是一个带有标签keyvalue 的元组数组
    let values: KeyValuePairs = ["key1": "value1", "key2": "value2"] 当您将其打印到控制台时,您会意识到这只是一个元组! print(values[0]) 将显示 (key: "key1", value: "value1")
  • 请看OrderedDictionary from The Swift Collections https://www.kodeco.com/24803770-getting-started-with-the-swift-collections-package
  • 您是否考虑过使用简单结构的数组来代替?
struct Document {
  let body: String?
  let isDeleted: Bool?
  let id: String?
  let isCompleted: Bool?
...
}

【讨论】:

  • 感谢您的提示!我认为第一点可以工作,但我想我可能会失去一些性能,因为它是一个数组并且可以通过索引访问。关于你的最后一点,我尝试解释(我想很糟糕)但我做不到像这样的结构,因为表格需要为各种集合绘制。每个集合包含不同的键,因此并非所有集合都有 isDeleted、body 等。
【解决方案2】:

这是一些示例代码,展示了如何处理不同的集合数据, 每个都有不同的键。并且仍然将所有结果放入[Document]的数组中, 然后显示在视图中。

struct ContentView: View {
    @StateObject var viewModel = DocumentsViewModel()
    
    // display one collection
    func listCollection(_ name: String) -> some View {
        VStack {
            if let docs = viewModel.docCollections[name] {
                Text(name).foregroundColor(.blue)
                ScrollView {
                    ForEach(docs) { doc in
                        HStack {
                            ForEach(Array(doc.data.keys), id: .self) { key in
                                HStack {
                                    Text(key).foregroundColor(.red)
                                    if let val = doc.data[key] as? NSObject {
                                        Text("(val)")
                                    }
                                }
                            }
                        }
                        Divider()
                    }
                }
            } else {
                Text("no data for collection (name)")
            }
        }
    }
    
    var body: some View {
        ScrollView {
            listCollection("first collection")
            Divider()
            listCollection("second collection")
        }
        .onAppear {
            viewModel.getDocsFor("first collection")
            viewModel.getDocsFor("second collection")
        }
    }
}

class DocumentsViewModel : ObservableObject {
    // the key as the name of the collection, and [Document] as the values
    @Published var docCollections: [String : [Document]] = [:]
    
    // data for testing, "first collection"
    let jsonData1 = """
 [
 {
 "body": "Go Shopping",
 "isDeleted": false,
 "_id": "6300b3",
 "isCompleted": false
 },
{
 "body": "Go xxxx",
 "isDeleted": false,
 "_id": "1234",
 "isCompleted": true
 },
{
 "body": "Go yyyy",
 "isDeleted": true,
 "_id": "7523",
 "isCompleted": false
 }
 ]
"""
    
    // data for "second collection"
    let jsonData2 = """
 [
 {
 "xbody": "Go X",
 "xbool": false,
 "_id": "1600b3",
 "xopt": "TTTTT"
 },
 {
 "xbody": "XXXXX",
 "xbool": true,
 "_id": "6468",
 "xopt": null
 },
 {
 "xbody": "UUUUUU",
 "xbool": false,
 "_id": "864",
 "xopt": "ertyuu"
 }
 ]
"""
    // simulated database fetching of any collection data
    func getDocsFor(_ collectionName: String) {
        // for testing
        var jsonData = jsonData1    // "first collection"
        if collectionName == "second collection" { jsonData = jsonData2 }
        
        if let data = jsonData.data(using: .utf8) {
            do {
                docCollections[collectionName] = try JSONDecoder().decode([Document].self, from: data)
            } catch {
                print("decoding error: (error)")
            }
        }
    }
    
}

struct Document: Identifiable, Decodable {
    let id = UUID()
    var data: [String: Any?] = [:]
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: DynamicKey.self)
        container.allKeys.forEach { key in
            if let theString = try? container.decode(String.self, forKey: key) {
                self.data[key.stringValue] = theString
            }
            if let theInt = try? container.decode(Int.self, forKey: key) {
                self.data[key.stringValue] = theInt
            }
            if let theDouble = try? container.decode(Double.self, forKey: key) {
                self.data[key.stringValue] = theDouble
            }
            if let theBool = try? container.decode(Bool.self, forKey: key) {
                self.data[key.stringValue] = theBool
            }
        }
    }
    
}

struct DynamicKey: CodingKey {
    var intValue: Int?
    init?(intValue: Int) {
        self.intValue = intValue
        self.stringValue = ""
    }
    
    var stringValue: String
    init?(stringValue: String) {
        self.stringValue = stringValue
        self.intValue = nil
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-15
    • 2017-07-21
    • 1970-01-01
    • 2016-06-04
    • 2020-07-04
    • 2012-03-11
    • 2014-08-09
    相关资源
    最近更新 更多