SwiftUI iterating through dictionary with ForEach

RPa*_*l99 4 dictionary swift swiftui

Is there a way to iterate through a Dictionary in a ForEach loop? Xcode says

Generic struct 'ForEach' requires that '[String : Int]' conform to 'RandomAccessCollection'

so is there a way to make Swift Dictionaries conform to RandomAccessCollection, or is that not possible because Dictionaries are unordered?

One thing I've tried is iterating the dictionary's keys:

let dict: [String: Int] = ["test1": 1, "test2": 2, "test3": 3]
...
ForEach(dict.keys) {...}
Run Code Online (Sandbox Code Playgroud)

But keys is not an array of Strings, it's type is Dictionary<String, Int>.Keys (not sure when that was changed). I know I could write a helper function that takes in a dictionary and returns an array of the keys, and then I could iterate that array, but is there not a built-in way to do it, or a way that's more elegant? Could I extend Dictionary and make it conform to RandomAccessCollection or something?

小智 83

You can sort your dictionary to get (key, value) tuple array and then use it.

struct ContentView: View {
    let dict = ["key1": "value1", "key2": "value2"]
    
    var body: some View {
        List {
            ForEach(dict.sorted(by: >), id: \.key) { key, value in
                Section(header: Text(key)) {
                    Text(value)
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我得到:“通用结构‘ForEach’要求 [String : [String]] 符合‘RandomAccessCollection’”如果我不排序,并且“类型‘[String: [String]] 不能符合‘Comparable’”如果我试着 (10认同)
  • `ForEach(Array(dict.keys), id: \.self)` 也可以工作,不需要排序。 (7认同)
  • 只要明白,如果您使用这种方法,您的数据必须保持静态。无绑定等 (3认同)
  • `id: \.self` 或 `id: \.key` 有什么作用?它们是一样的吗? (2认同)

paw*_*222 27

有序字典

在 WWDC21 上,Apple 宣布了Collections其中包含的软件包OrderedDictionary(除其他外)。

现在,我们只需要替换:

let dict: [String: Int] = ["test1": 1, "test2": 2, "test3": 3]
Run Code Online (Sandbox Code Playgroud)

和:

let dict: OrderedDictionary = ["test1": 1, "test2": 2, "test3": 3]
Run Code Online (Sandbox Code Playgroud)

或者,我们可以从另一个初始化:

let dict: [String: Int] = ["test1": 1, "test2": 2, "test3": 3]
let orderedDict = OrderedDictionary(uniqueKeys: dict.keys, values: dict.values)
Run Code Online (Sandbox Code Playgroud)

请注意,由于dict是无序的,因此您可能需要对其进行排序orderedDict以强制执行一致的顺序。


以下是我们如何在 SwiftUI 视图中使用它的示例:

import Collections
import SwiftUI

struct ContentView: View {
    let dict: OrderedDictionary = ["test1": 1, "test2": 2, "test3": 3]

    var body: some View {
        VStack {
            ForEach(dict.keys, id: \.self) {
                Text("\($0)")
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:目前作为单独的Collections提供,因此您需要将其导入到您的项目中。


您可以在这里找到更多信息:

  • 只是想说谢谢分享这个。我不知道苹果发布了这个。 (2认同)

And*_*era 20

由于它是无序的,唯一的方法就是将它放入一个数组中,这很简单。但是数组的顺序会有所不同。

struct Test : View {
let dict: [String: Int] = ["test1": 1, "test2": 2, "test3": 3]
var body: some View {
    let keys = dict.map{$0.key}
    let values = dict.map {$0.value}

    return List {
        ForEach(keys.indices) {index in
            HStack {
                Text(keys[index])
                Text("\(values[index])")
            }
        }
    }
}
}

#if DEBUG
struct Test_Previews : PreviewProvider {
    static var previews: some View {
        Test()
    }
}
#endif
Run Code Online (Sandbox Code Playgroud)


abe*_*ehr 7

如果您使用swift-collection的 OrderedDictionary (OrderedCollections 的一部分),您可以执行以下操作:

var states: OrderedDictionary<Player, String>
ForEach(states.elements, id:\.key) { player, state in
    PlayerRow(player: player, state: state)
}
Run Code Online (Sandbox Code Playgroud)

确保关键对象是可识别的,否则请调整 ForEach 上的 id 参数。


J. *_*Doe 6

简单的回答:不。

正如您正确指出的那样,字典是无序的。ForEach监视他的收藏中的更改。此更改包括插入,删除,移动和更新。如果发生任何这些更改,将触发更新。参考:https : //developer.apple.com/videos/play/wwdc2019/204/ at 46:10:

ForEach自动监视其收藏中的更改

我建议你看说话:)

您不能使用ForEach,因为:

  1. 它观看收藏并监视动作。不可修改的字典无法使用。
  2. 当重用视图时(例如UITableView,可以回收单元的情况下,重用单元,a List支持UITableViewCells,我认为a ForEach在做相同的事情),它需要计算要显示的单元。它通过查询数据源的索引路径来实现。从逻辑上讲,如果数据源是无序的,则索引路径是无用的。


Dim*_*ovo 6

Xcode: 11.4.1~

    ...
    var testDict: [String: Double] = ["USD:": 10.0, "EUR:": 10.0, "ILS:": 10.0]
    ...
    ForEach(testDict.keys.sorted(), id: \.self) { key in
        HStack {
            Text(key)
            Text("\(testDict[key] ?? 1.0)")
        }
    }
    ...
Run Code Online (Sandbox Code Playgroud)

更多详情:

final class ViewModel: ObservableObject {
    @Published
    var abstractCurrencyCount: Double = 10
    @Published
    var abstractCurrencytIndex: [String: Double] = ["USD:": 10.0, "EUR:": 15.0, "ILS:": 5.0]
}

struct SomeView: View {
    @ObservedObject var vm = ViewModel()
    var body: some View {
        NavigationView {
            List {
                ForEach(vm.abstractCurrencytIndex.keys.sorted(), id: \.self) { key in
                    HStack {
                        Text(String(format: "%.2f", self.vm.abstractCurrencyCount))
                        Text("Abstract currency in \(key)")
                        Spacer()
                        Text(NumberFormatter.localizedString(from: NSNumber(value: self.vm.abstractCurrencytIndex[key] ?? 0.0), number: .currency))
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)