I have the following component that renders a grid of semi transparent characters:
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
is an EnvironmentObject
Whenever settings
is updated the Text in the outermost VStack
is correctly updated. However, the rest of the view is not updated (Grid has same dimenstions as before). Why is this?
Second question:
Why is it not possible to access the i
in the inner ForEach
-loop and pass it as a argument to the function?
I get an error at the outer ForEach
-loop:
Generic parameter 'Data' could not be inferred
should only be used for *constant* data. Instead conform data to Identifiable or use ForEach(_:id:content:) and provide an explicit id! At this point, we might have actually solved the problem. There are no more warnings being emitted, and things might continue to work perfectly fine even as we mutate our Note array.
An important feature of foreach is the %:% operator. I call this the nesting operator because it is used to create nested foreach loops. Like the %do% and %dopar% operators, it is a binary operator, but it operates on two foreach objects.
For-each cannot be used to initialize any array or Collection, because it loops over the current contents of the array or Collection, giving you each value one at a time. The variable in a for-each is not a proxy for an array or Collection reference.
C# provides an easy to use and more readable alternative to for loop, the foreach loop when working with arrays and collections to iterate through the items of arrays/collections. The foreach loop iterates through each item, hence called foreach loop.
Your ForEach needs id: \.self
added after your range.
ForEach has several initializers. You are using
init(_ data: Range<Int>, @ViewBuilder content: @escaping (Int) -> Content)
where data
must be a constant.
If your range may change (e.g. you are adding or removing items from an array, which will change the upper bound), then you need to use
init(_ data: Data, id: KeyPath<Data.Element, ID>, content: @escaping (Data.Element) -> Content)
You supply a keypath to the id
parameter, which uniquely identifies each element that ForEach
loops over. In the case of a Range<Int>
, the element you are looping over is an Int
specifying the array index, which is unique. Therefore you can simply use the \.self
keypath to have the ForEach identify each index element by its own value.
Here is what it looks like in practice:
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])")
//Note: If you want more than one views here, you need a VStack or some container, or will throw errors
}
}
}
}
If you run that, you'll see that as you press the button to add items to the array, they will appear in the VStack
automatically. If you remove "id: \.self
", you'll see your original error:
`ForEach(_:content:)` should only be used for *constant* data.
Instead conform data to `Identifiable` or use `ForEach(_:id:content:)`
and provide an explicit `id`!"
ForEach should only be used for constant data. So it is only evaluated once by definition. Try wrapping it in a List and you will see errors being logged like:
ForEach, Int, TupleView<(Spacer, HStack, Int, TupleView<(Spacer, Text, Spacer)>>>, Spacer)>> count (7) != its initial count (0).
ForEach(_:content:)
should only be used for constant data. Instead conform data toIdentifiable
or useForEach(_:id:content:)
and provide an explicitid
!
I was surprised by this as well, and unable to find any official documentation about this limitation.
As for why it is not possible for you to access the i in the inner ForEach-loop, I think you probably have a misleading compiler error on your hands, related to something else in the code that is missing in your snippets. It did compile for me after completing the missing parts with a best guess (Xcode 11.1, Mac OS 10.14.4).
Here is what I came up with to make your ForEach go over something Identifiable:
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)")
}
}
}
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With