I'm trying to update LazyVGrid item properties when tapped and it worked on plain Ints but stopped working when numbers are wrapped in collection.
code that doesn't work:
import SwiftUI
struct Item : Identifiable, Hashable {
var id: Int
}
let data = (1...12).map { index in
Item(id: index)
}
struct ZIndexInLazyVGridUpdateContainerExperiment: View {
@State private var selectedItemIndex: Int = 5
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
ScrollView {
LazyVGrid(columns: [GridItem(), GridItem(), GridItem()]) {
ForEach(data) { item in
Rectangle()
.fill(Color(
red: 0,
green: 0.5,
blue: selectedItemIndex == item.id ? 1 : Double(item.id)/24.0 + 0.5))
.zIndex(selectedItemIndex == item.id ? 1 : 0)
.frame(width: 180, height: 180)
.border(.black)
.rotationEffect(.degrees(45))
.onTapGesture {
selectedItemIndex = item.id
print("selected item \(selectedItemIndex)")
}
}
.id(selectedItemIndex)
}
}
}
}
}
code that works, that was slightly changed:
import SwiftUI
struct ZIndexInLazyVGridUpdateExperiment: View {
@State private var selectedItemIndex: Int = 5
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
ScrollView {
LazyVGrid(columns: [GridItem(), GridItem(), GridItem()]) {
ForEach(1..<13) { index in
Rectangle()
.fill(Color(
red: selectedItemIndex == index ? 1 : Double(index)/24.0 + 0.5,
green: 0,
blue: 0))
.zIndex(selectedItemIndex == index ? 1 : 0)
.frame(width: 180, height: 180)
.border(.black)
.rotationEffect(.degrees(45))
.onTapGesture {
selectedItemIndex = index
print("selected item \(selectedItemIndex)")
}
}
.id(selectedItemIndex)
}
}
}
}
}
Previously (in working code version), item, when tapped, would print to console, update rectangle color and update zIndex. After wrapping Int in Item struct, tapping still is logged, but nothing in LazyVGrid changes visually. I can't figure out what went wrong.
As Workingdog showed, you need to have the selected id
on the LazyVGrid
, not on the ForEach
. I suspect that the lazy optimisation part of the LazyVGrid
works out that none of the bound IDs are changing in the ForEach, so it doesn't redraw the view.
If you remove the LazyVGrid
so that you only have the rectangles in the scroll view, changing the id
of the ForEach
is sufficient.
In addition to moving the id
for the selected item to the LazyVGrid
I would avoid using the .id
property; this doesn't change the behaviour of your code, but it is cleaner. Identifiable
means you can compare directly
import SwiftUI
struct Item : Identifiable, Hashable {
var id: Int
}
let data = (1...12).map { index in
Item(id: index)
}
struct ContentView: View {
@State private var selectedItem = data[5]
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
ScrollView {
LazyVGrid(columns: [GridItem(), GridItem(), GridItem()]) {
ForEach(data) { item in
Rectangle()
.fill(Color(
red: 0,
green: 0.5,
blue: selectedItem == item ? 1 : Double(item.id)/24.0 + 0.5))
.zIndex(item == selectedItem ? 1 : 0)
.frame(width: 180, height: 180)
.border(.black)
.rotationEffect(.degrees(45))
.onTapGesture {
selectedItem = item
print("selected item \(selectedItem.id)")
}
}
}
.id(selectedItem)
}
}
}
}