Iterate a Grid of Views Swiftui

Iterate a grid of views SwiftUI

I don't think there's anything specific in SwiftUI that prevents this. I think this is what you're trying to accomplish?

struct ContentView : View {
let cards = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"]
var body: some View {
ScrollView{
ForEach(0..<cards.count/3) { row in // create number of rows
HStack {
ForEach(0..<3) { column in // create 3 columns
Text(self.cards[row * 3 + column])
}
}
}
}
}
}

I don’t understand how array[row * 3+ column] creates multidimensional array

cards is only a 1-dimensional array. It is just a basic array, indexed using [].

In this example, a grid is created with cards.count / 3 rows and 3 columns. The grid goes in the order of left-to-right and then down:

Order

You could think of this as the following, where green is row and blue is column:

row × 3 + column = index in cards
































A: 0 × 3 + 0 = 0B: 0 × 3 + 1 = 1C: 0 × 3 + 2 = 2
D: 1 × 3 + 0 = 3E: 1 × 3 + 1 = 4F: 1 × 3 + 2 = 5
G: 2 × 3 + 0 = 6H: 2 × 3 + 1 = 7I: 2 × 3 + 2 = 8
J: 3 × 3 + 0 = 9K: 3 × 3 + 1 = 10L: 3 × 3 + 2 = 11

SwiftUI Grid Index out of Range

An easy way to avoid any indexOutOfBounds is just to check if the index is out of bounds before doing the operation...

So make this change:

ForEach(0..<self.cols,id:\.self) { column in
let card = self.colrow(col: column, row: row)
if (card < self.cards.count) {
Text(self.cards[card])
}
}

This will leave your last row potentially unfilled but it shouldn't crash

How to create Grids View in SwiftUI inside a ScrollView?

struct SimpleGridView: View {
@State var yourArray: [Int] = [1,2,3,4,5,6,7,8,9]
var columns: [GridItem] =
Array(repeating: .init(.flexible()), count: 2)
@State var selection: Int = 1
var body: some View {
VStack{
Picker(selection: $selection, label: Text("GridStyles"), content: {
Text("Easily grows").tag(1)
Text("manual").tag(2)
}).pickerStyle(SegmentedPickerStyle())

switch selection {
case 1:
ScrollView{
LazyVGrid(columns: columns){
//This doesnt have to be a loop but it would make it scalable
ForEach(yourArray, id: \.self){ item in
Button(action: {
print(item.description)
}, label: {
Rectangle().overlay(Text(item.description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealWidth: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, minHeight: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealHeight: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)

})
}
}
}
case 2:
VStack{
HStack{
Button(action: {
print(yourArray[0].description)
}, label: {
Rectangle().overlay(Text(yourArray[0].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealWidth: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, minHeight: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealHeight: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)

})
Button(action: {
print(yourArray[1].description)
}, label: {
Rectangle().overlay(Text(yourArray[1].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealWidth: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, minHeight: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealHeight: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)

})
}
HStack{
Button(action: {
print(yourArray[2].description)
}, label: {
Rectangle().overlay(Text(yourArray[2].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealWidth: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, minHeight: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealHeight: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)

})
Button(action: {
print(yourArray[3].description)
}, label: {
Rectangle().overlay(Text(yourArray[3].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealWidth: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, minHeight: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealHeight: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)

})
}
HStack{
Button(action: {
print(yourArray[4].description)
}, label: {
Rectangle().overlay(Text(yourArray[4].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealWidth: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, minHeight: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealHeight: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)

})
Button(action: {
print(yourArray[5].description)
}, label: {
Rectangle().overlay(Text(yourArray[5].description).foregroundColor(.white)).foregroundColor(.blue).frame(minWidth: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealWidth: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, minHeight: /*@START_MENU_TOKEN@*/0/*@END_MENU_TOKEN@*/, idealHeight: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, maxHeight: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)

})
}
}

default:
Text("unknown selection")
}
}
}
}

View is not rerendered in Nested ForEach loop

TL;DR

Your ForEach needs id: \.self added after your range.

Explanation

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`!"


Related Topics



Leave a reply



Submit