SwiftUI iterating through dictionary with ForEach
OrderedDictionary
At WWDC21 Apple announced the Collections
package that includes OrderedDictionary
(among others).
Now, we just need to replace:
let dict: [String: Int] = ["test1": 1, "test2": 2, "test3": 3]
with:
let dict: OrderedDictionary = ["test1": 1, "test2": 2, "test3": 3]
Alternatively, we can init one from another:
let dict: [String: Int] = ["test1": 1, "test2": 2, "test3": 3]
let orderedDict = OrderedDictionary(uniqueKeys: dict.keys, values: dict.values)
Just note that because dict
is unordered, you may want to sort the orderedDict
to enforce the consistent order.
Here is an example how we can use it in a SwiftUI View:
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)")
}
}
}
}
Note: Currently Collections
is available as a separate package, so you need to import it to your project.
You can find more information here:
- WWDC session - Meet the Swift Algorithms and Collections packages
- swift.org announcement - Introducing Swift Collections
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)
}
}
}
}
}
Since it's unordered, the only way is to put it into an array, which is pretty simple. But the order of the array will vary.
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
Simple answer: no.
As you correctly pointed out, a dictionary is unordered. The ForEach watches its collection for changes. These changes includes inserts, deletions, moves and update. If any of those changes occurs, an update will be triggered. Reference: https://developer.apple.com/videos/play/wwdc2019/204/ at 46:10:
A ForEach automatically watches for changes in his collection
I recommend you watch the talk :)
You can not use a ForEach because:
- It watches a collection and monitors movements. Impossible with an unorered dictionary.
- When reusing views (like a
UITableView
reuses cells when cells can be recycled, aList
is backed byUITableViewCells
, and I think aForEach
is doing the same thing), it needs to compute what cell to show. It does that by querying an index path from the data source. Logically speaking, an index path is useless if the data source is unordered.