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:
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 = 0 | B: 0 × 3 + 1 = 1 | C: 0 × 3 + 2 = 2 |
D: 1 × 3 + 0 = 3 | E: 1 × 3 + 1 = 4 | F: 1 × 3 + 2 = 5 |
G: 2 × 3 + 0 = 6 | H: 2 × 3 + 1 = 7 | I: 2 × 3 + 2 = 8 |
J: 3 × 3 + 0 = 9 | K: 3 × 3 + 1 = 10 | L: 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
Prepare for Segue with Array - Xcode 8.0 Swift 3.0
How to Notify a Queue in Swift (Gcd)
Swift Uikit Dynamics Add Collision Boundary. After Rotation Does Not Work Correctly
Need Clarification for Swift Type Properties
Show Datepicker Hourandminute on Print Statement Swiftui
Make Swiftui Rectangle Same Height or Width as Another Rectangle
How to Convert This Date Format in Swift
Decode/Encode Dictionary Keyed by Date
Handle Single Click and Double Click While Updating the View
How to Set Up Multiple Combine Timer Publishers
Updating Switfui View When Coredata Changes
Failable Initializers with Codable
Where to Implement Nsvaluetransformer for Core Data in Swift
Why Function Return Nil Firebase Swift
How to Convert [Any] to Nsarray
Spritekiit Swift: Touch a Moving Object