skip to Main Content

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.

enter image description here

2

Answers


  1. try this approach attaching .id(selectedItemIndex) to the LazyVGrid

    LazyVGrid(columns: [GridItem(), GridItem(), GridItem()]) {
     ...
    }.id(selectedItemIndex)  // <--- here  
    

    EDIT-1:

    You could also try a similar approach with this example code:

    struct Item: Identifiable, Hashable {
        let id = UUID()  // <--- here
        var value: Int
    }
    
    struct ZIndexInLazyVGridUpdateContainerExperiment: View {
        @State private var selectedId: UUID = UUID() // <--- here
        
        let data: [Item] = (1...12).map { Item(value: $0) }
        
        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: selectedId == item.id ? 1 : Double(item.value)/24.0 + 0.5)) // <--- here
                                .zIndex(selectedId == item.id  ? 1 : 0)
                                .frame(width: 180, height: 180)
                                .border(.black)
                                .rotationEffect(.degrees(45))
                                .onTapGesture {
                                    selectedId = item.id
                                }
                        }
                    }
                    .id(selectedId)  // <--- here
                }
            }
        }
    }
    
    struct ContentView: View {
        var body: some View {
            ZIndexInLazyVGridUpdateContainerExperiment()
        }
    }
    
    Login or Signup to reply.
  2. 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)
                }
            }
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search