SwiftUI使用ForEach遍历字典

时间:2019-06-19 20:40:35

标签: swift dictionary swiftui

是否有一种方法可以在Dictionary循环中迭代ForEach? Xcode说

  

通用结构'ForEach'要求'[String:Int]'符合'RandomAccessCollection'

那么有没有办法使Swift字典符合RandomAccessCollection,或者由于字典是无序的而不可能吗?

我尝试过的一件事是迭代字典的键:

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

但是keys不是String的数组,它的类型是Dictionary<String, Int>.Keys(不确定何时更改)。我知道我可以编写一个辅助函数,该函数接受字典并返回键的数组,然后可以迭代该数组,但是没有内置的方法或更优雅的方法吗?我可以扩展Dictionary并使其符合RandomAccessCollection之类的东西吗?

9 个答案:

答案 0 :(得分:5)

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)")
        }
    }
    ...

更多细节:

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))
                    }
                }
            }
        }
    }
}

答案 1 :(得分:3)

简单答案:否。

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

  

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

我建议您观看讲话:)

您不能使用ForEach,因为:

  1. 它监视收藏并监视动作。不可修改的字典是不可能的。
  2. 重用视图时(例如const admin = require('firebase-admin'); admin.initializeApp({ credential: admin.credential.applicationDefault() }); const db = admin.firestore(); /** * Responds to any HTTP request. * * @param {!express:Request} req HTTP request context. * @param {!express:Response} res HTTP response context. */ exports.helloWorld = (req, res) => { /* let message = req.query.message || req.body.message || 'Hello World!'; res.status(200).send(message); */ return db.collection("users").add({ first: "Ada", last: "Lovelace", born: 1815 }); }; 重用可回收单元格的单元格时,{ "name": "sample-http", "version": "0.0.1", "dependencies": { "semver": "^5.5.1", "@google-cloud/firestore": "^1.3.0", "firebase-admin": "^7.1.1" } } UITableView支持,而我认为List也在这样做东西),它需要计算要显示的单元格。它通过查询数据源的索引路径来实现。从逻辑上讲,如果数据源是无序的,则索引路径是无用的。

答案 2 :(得分:2)

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

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

答案 3 :(得分:2)

有序字典

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

现在,我们只需要替换:

let dict: [String: Int] = ["test1": 1, "test2": 2, "test3": 3]

与:

let dict: OrderedDictionary = ["test1": 1, "test2": 2, "test3": 3]

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

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

请注意,因为 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)")
            }
        }
    }
}

注意:目前 Collections 可作为单独的 package 使用,因此您需要将它import 用于您的项目。


您可以在此处找到更多信息:

答案 4 :(得分:0)

您可以对字典进行排序以获得(键,值)元组数组,然后使用它

let dict: [(key: String, value: Int)] = ["test1": 1, "test2": 2, "test3": 3].sorted{$0.key < $1.key}

SwiftUI列表用法

List {
     ForEach(dict, id: \.0) { index, item in
         Section(header: Text(index)) {
             Text(item.description)
         }
     }
}

但是,如果您想按要求使用RandomAccessCollection

class DictionaryRandomAccessCollection<Key, Value>: RandomAccessCollection {

    typealias Element = (Key, Value)
    var startIndex: Int = 0
    var endIndex: Int { dataSource.count }

    private(set) var dataSource: [(key: Key, value: Value)]

    init(dataSource: [(Key, Value)]) {
        self.dataSource = dataSource
    }
    subscript(position: Int) -> Element {
        return (key: dataSource[position].key, value: dataSource[position].value)

    }
}

初始化

var collection: DictionaryRandomAccessCollection<String, Int> = DictionaryRandomAccessCollection(dataSource: dict)

答案 5 :(得分:0)

我也试图用enum / Int对​​字典来解决这个问题。我可以按照Andre的建议将其有效地转换为数组,但是我没有使用map而是直接转换了键。

enum Fruits : Int, CaseIterable {
    case Apple = 0
    case Banana = 1
    case Strawberry = 2
    case Blueberry = 3
}

struct ForEachTest: View {
    var FruitBasket : [Fruits: Int] = [Fruits.Apple: 5, Fruits.Banana : 8, Fruits.Blueberry : 20]
    var body: some View {
        VStack {
            ForEach([Fruits](FruitBasket.keys), id:\Fruits.hashValue) { f in
                Text(String(describing: f) + ": \(self.FruitBasket[f]!)")
            }
        }
    }
}

struct ForEachTest_Previews: PreviewProvider {
    static var previews: some View {
        ForEachTest()
    }
}

答案 6 :(得分:0)

这是我的实现方式:

struct CartView: View {
    var cart:[String: Int]

    var body: some View {
        List {
            ForEach(cart.keys.sorted()) { key in
                Text(key)
                Text("\(cart[key]!)")
            }
        }
    }
}

第一个文本视图将输出为字符串的键。 第二个“文本视图”将在该键(即Int)处输出Dict的值。 !接下来的操作是解开包含此Int的Optional。在生产中,您将对此Optional进行检查,以更安全地对其进行包装,但这是概念的证明。

答案 7 :(得分:0)

从这篇文章的一些答案中使用代码遇到的语法错误帮助我为自己的问题找出了解决方案...

使用包含以下内容的字典

  • key = a CoreDataEntity;
  • value =关系中该实体的实例计数(类型NSSet)。

我强调了@ J.Doe的回答,并指出无序/随机集合Dictionary可能不是与表视图单元格一起使用的最佳解决方案(AppKit NSTableView / UIKit {{1} } / SwiftUI UITableView行)。

随后,我将重建代码以代替使用数组。

但是如果您必须使用List,这是我的可行解决方案:

Dictionary

答案 8 :(得分:0)

不,您不能将 ForEach ViewDictionary 一起使用。您可以尝试,但它可能会崩溃,特别是如果您尝试从实际数据循环不同的数组时使用 id: .\self hack。如图 in the documentation 所示,要正确使用 ForEach View,您需要一个“已识别数据集合”,您可以通过创建符合 Identifiable 的自定义结构并使用包含以下内容的数组来创建该集合结构如下:

private struct NamedFont: Identifiable {
    let name: String
    let font: Font
    var id: String { name } // or let id = UUID()
}

private let namedFonts: [NamedFont] = [
    NamedFont(name: "Large Title", font: .largeTitle),
    NamedFont(name: "Title", font: .title),
    NamedFont(name: "Headline", font: .headline),
    NamedFont(name: "Body", font: .body),
    NamedFont(name: "Caption", font: .caption)
]

var body: some View {
    ForEach(namedFonts) { namedFont in
        Text(namedFont.name)
            .font(namedFont.font)
    }
}