SwiftUI 如何使用 ForEach 循环创建视图?

通常,您会发现要遍历序列以创建视图,而在 SwiftUI 中,这是使用 ForEach 完成的。

重要提示:很容易查看 ForEach 并认为它与 Swift 序列上的 forEach() 方法相同,但事实并非如此。

SwiftUI 中的 ForEach 本身就是一个视图结构,这意味着您可以根据需要直接从视图主体中返回它。 您为它提供了一组项目,还可能需要告诉 SwiftUI 它如何唯一地标识每个项目,以便它知道在值更改时如何更新它们。 您还向它传递了一个函数,该函数要运行以为循环中的每个项目创建一个视图。

对于范围上的简单循环,您可以将范围直接传递到 ForEach 中,并告诉 Swift 将每个数字用作项目的唯一标识符。 例如,此计数从 10 减少到 1,然后在末尾添加一条消息:

VStack(alignment: .leading) {
    ForEach((1...10).reversed(), id: \.self) {
        Text("\($0)…")
    }

    Text("Ready or not, here I come!")
}

.id(:\.self) 部分是必需的,以便 SwiftUI 可以唯一地标识数组中的每个元素–这意味着,如果添加或删除一项,SwiftUI 会确切知道哪一个。

您可以使用这种方法来创建任何类型的循环。 例如,此代码创建一个由三种颜色组成的数组,将它们全部循环,并使用每种颜色名称和颜色值创建文本视图:

struct ContentView: View {
    let colors: [Color] = [.red, .green, .blue]

    var body: some View {
        VStack {
            ForEach(colors, id: \.self) { color in
                Text(color.description.capitalized)
                    .padding()
                    .background(color)
            }
        }
    }
}

使用 \.self 告诉 Swift,每个项目都使用其自己的值进行唯一标识。 因此,如果您有数组 [1、2、3] 并用 \.self 标识每个值,则意味着第一项具有标识符 1,第二个 2 和第三个标识符。

如果数组中有自定义类型,则应将id与类型内的任何属性一起使用,以对其进行唯一标识。 例如,您可以创建一个结构,其中id属性是UUID,这意味着它可以保证是唯一的-对于我们的目的而言是完美的。 我们可以创建一个这样的结构,然后像这样使用它:

struct SimpleGameResult {
    let id = UUID()
    let score: Int
}

struct ContentView: View {
    let results = [
        SimpleGameResult(score: 8),
        SimpleGameResult(score: 5),
        SimpleGameResult(score: 10)
    ]

    var body: some View {
        VStack {
            ForEach(results, id: \.id) { result in
                Text("Result: \(result.score)")
            }
        }
    }
}

这告诉 SwiftUI 它可以通过查看它们的 id 属性来区分 ForEach 内部的视图。

或者,如果您创建的结构符合 Identifiable 协议,则只需编写 ForEach(results)。 遵守该协议意味着添加一个 id 属性,该属性唯一地标识每个对象,在我们的例子中,我们已经拥有了它。 因此,此代码实现了相同的结果:

struct IdentifiableGameResult: Identifiable {
    var id = UUID()
    var score: Int
}

struct ContentView: View {
    let results = [
        IdentifiableGameResult(score: 8),
        IdentifiableGameResult(score: 5),
        IdentifiableGameResult(score: 10)
    ]

    var body: some View {
        VStack {
            ForEach(results) { result in
                Text("Result: \(result.score)")
            }
        }
    }
}