视图未在嵌套的ForEach循环中重新呈现

时间:2019-10-22 12:41:54

标签: swiftui

我有以下组件可呈现半透明字符的网格:

    var body: some View {
        VStack{
            Text("\(self.settings.numRows) x \(self.settings.numColumns)")
            ForEach(0..<self.settings.numRows){ i in
                Spacer()
                    HStack{
                        ForEach(0..<self.settings.numColumns){ j in
                            Spacer()
                            // why do I get an error when I try to multiply i * j
                            self.getSymbol(index:j)
                            Spacer()
                        }
                    }
                Spacer()
            }
        }
    }

settings是一个EnvironmentObject

只要更新settings,最外面的VStack中的文本就会正确更新。但是,视图的其余部分不会更新(Grid具有与以前相同的尺寸)。为什么会这样?

第二个问题: 为什么无法访问内部i循环中的ForEach并将其作为参数传递给函数?

在外部ForEach循环中出现错误:

  

无法推断出通用参数“数据”

2 个答案:

答案 0 :(得分:2)

TL; DR

您的ForEach需要在您的范围之后添加id: \.self

说明

ForEach有several initializers。您正在使用

init(_ data: Range<Int>, @ViewBuilder content: @escaping (Int) -> Content)

其中data 必须为常数。

如果您的范围可能会发生变化(例如,您正在从数组中添加或删除项目,这会更改上限),那么您需要使用

init(_ data: Data, id: KeyPath<Data.Element, ID>, content: @escaping (Data.Element) -> Content)

您提供了id参数的键路径,该参数唯一地标识了ForEach循环的每个元素。对于Range<Int>,要循环的元素是一个Int,它指定唯一的数组索引。因此,您可以简单地使用\.self键路径,让ForEach通过其自己的值来标识每个索引元素。

这是实际的样子:

struct ContentView: View {
    @State var array = [1, 2, 3]

    var body: some View {
        VStack {
            Button("Add") {
                self.array.append(self.array.last! + 1)
            }

            // this is the key part  v--------v
            ForEach(0..<array.count, id: \.self) { index in
                Text("\(index): \(self.array[index])")
            }
        }
    }
}

如果运行该命令,则会看到按下按钮向数组添加项目时,它们将自动出现在VStack中。如果删除“ id: \.self”,则会看到原始错误:

`ForEach(_:content:)` should only be used for *constant* data. 
Instead conform data to `Identifiable` or use `ForEach(_:id:content:)`
and provide an explicit `id`!"

答案 1 :(得分:1)

ForEach仅应用于恒定数据。因此,根据定义它仅被评估一次。尝试将其包装在列表中,您将看到错误记录,例如:

  

ForEach,Int,TupleView <(Spacer,HStack,Int,TupleView <(Spacer,Text,Spacer)>>>,Spacer)>> count(7)!=其初始计数(0)。 ForEach(_:content:)仅应用于恒定数据。而是使数据符合Identifiable或使用ForEach(_:id:content:)并提供明确的id

我也对此感到惊讶,并且无法找到有关此限制的任何官方文档。

关于为什么您无法在内部ForEach循环中访问i的原因,我认为您可能会遇到误导性的编译器错误,这与代码段中缺少的其他内容有关。最好的猜测(Xcode 11.1,Mac OS 10.14.4)完成后,它确实为我编译了。

这是我想出的办法,目的是使您的ForEach能够通过一些可识别的事情:



struct SettingsElement: Identifiable {
    var id: Int { value }

    let value: Int

    init(_ i: Int) { value = i }
}

class Settings: ObservableObject {
    @Published var rows = [SettingsElement(0),SettingsElement(1),SettingsElement(2)]
    @Published var columns = [SettingsElement(0),SettingsElement(1),SettingsElement(2)]
}

struct ContentView: View {
    @EnvironmentObject var settings: Settings

    func getSymbol(index: Int) -> Text { Text("\(index)") }

    var body: some View {
        VStack{
            Text("\(self.settings.rows.count) x \(self.settings.columns.count)")
            ForEach(self.settings.rows) { i in
                VStack {
                    HStack {
                        ForEach(self.settings.columns) { j in
                            Text("\(i.value) \(j.value)")
                        }
                    }
                }
            }
        }
    }
}