admin管理员组

文章数量:1301534

I have the following example view with no zIndex modifications. Currently, when selecting a grid item, it will animate to the center of the screen. However, this does not change zIndex and items will stay below others.

I tried adding ZStacks to various parts of the code, with a .zIndex() on the Rectangle, none of which worked.

The closest i got to a fix: adding .id() of the selected item to the LazyVGrid, with a .zindex conditionally setting it on the GeometryReader. While this sets hierarchy properly, each time an item is selected, the entire grid flashes and there is no animating the item from its grid position to the center.

struct SwiftUIView: View {
    let colors: [Color] = [.red, .blue, .green, .yellow, .purple, .orange]
    let columns = [GridItem(.flexible()), GridItem(.flexible())]
    
    @State private var selectedItem: Int? = nil
    @State private var selectedItemPosition: CGRect? = nil

    var body: some View {
        GeometryReader { screenGeometry in
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(colors.indices, id: \.self) { index in
                    GeometryReader { geometry in
                        let isSelected = selectedItem == index
                        
                        Rectangle()
                            .fill(colors[index])
                            .cornerRadius(12)
                            .frame(width: 150, height: 200)
                            .shadow(radius: isSelected ? 10 : 0)
                            .onTapGesture {
                                withAnimation(.spring(response: 0.5, dampingFraction: 0.7)) {
                                    if isSelected {
                                        selectedItem = nil
                                        selectedItemPosition = nil
                                    } else {
                                        selectedItem = index
                                        selectedItemPosition = geometry.frame(in: .global)
                                    }
                                }
                            }
                            .offset(
                                x: isSelected ? (screenGeometry.size.width / 2 - geometry.frame(in: .global).midX) : 0,
                                y: isSelected ? (screenGeometry.size.height / 2 - geometry.frame(in: .global).midY) : 0
                            )
                    }
                    .frame(height: 200) // Needed to ensure GeometryReader does not shrink
                }
            }
            .padding()
        }
    }
}

I have the following example view with no zIndex modifications. Currently, when selecting a grid item, it will animate to the center of the screen. However, this does not change zIndex and items will stay below others.

I tried adding ZStacks to various parts of the code, with a .zIndex() on the Rectangle, none of which worked.

The closest i got to a fix: adding .id() of the selected item to the LazyVGrid, with a .zindex conditionally setting it on the GeometryReader. While this sets hierarchy properly, each time an item is selected, the entire grid flashes and there is no animating the item from its grid position to the center.

struct SwiftUIView: View {
    let colors: [Color] = [.red, .blue, .green, .yellow, .purple, .orange]
    let columns = [GridItem(.flexible()), GridItem(.flexible())]
    
    @State private var selectedItem: Int? = nil
    @State private var selectedItemPosition: CGRect? = nil

    var body: some View {
        GeometryReader { screenGeometry in
            LazyVGrid(columns: columns, spacing: 20) {
                ForEach(colors.indices, id: \.self) { index in
                    GeometryReader { geometry in
                        let isSelected = selectedItem == index
                        
                        Rectangle()
                            .fill(colors[index])
                            .cornerRadius(12)
                            .frame(width: 150, height: 200)
                            .shadow(radius: isSelected ? 10 : 0)
                            .onTapGesture {
                                withAnimation(.spring(response: 0.5, dampingFraction: 0.7)) {
                                    if isSelected {
                                        selectedItem = nil
                                        selectedItemPosition = nil
                                    } else {
                                        selectedItem = index
                                        selectedItemPosition = geometry.frame(in: .global)
                                    }
                                }
                            }
                            .offset(
                                x: isSelected ? (screenGeometry.size.width / 2 - geometry.frame(in: .global).midX) : 0,
                                y: isSelected ? (screenGeometry.size.height / 2 - geometry.frame(in: .global).midY) : 0
                            )
                    }
                    .frame(height: 200) // Needed to ensure GeometryReader does not shrink
                }
            }
            .padding()
        }
    }
}
Share edited Feb 11 at 10:11 Benzy Neez 22.1k3 gold badges14 silver badges41 bronze badges asked Feb 11 at 5:04 Flynn Flynn 2131 silver badge8 bronze badges 0
Add a comment  | 

1 Answer 1

Reset to default 1

Changing the zIndex of items in a LazyVGrid does not work well, as demonstrated in this post. Changing the .id of the items is a way to work around the issue. However, this may make it harder to animate the changes, as you have found.

An alternative way to solve is to use .matchedGeometryEffect to position the visible items:

  • The grid contains hidden placeholders for the tiles in their normal positions.
  • The position of the centered item is also determined by a hidden placeholder. By showing this as a separate layer, there is no need to use a GeometryReader to determine the screen size, because it will be centered automatically.
  • The visible items are matched to the placeholders using their own index as id or, in the case of the selected item, using the id of the centered placeholder.
  • By using a second state variable to hold the index of the last selected item, an item that is moving back to its grid position can be given a zIndex that is higher than the other items too (but lower than the currently selected item). If this is not done, an item that is returning to its grid position will travel under the tiles that follow it in the layout.
struct SwiftUIView: View {
    let colors: [Color] = [.red, .blue, .green, .yellow, .purple, .orange]
    let columns = [GridItem(.flexible()), GridItem(.flexible())]
    let centeredItemId = -1

    @State private var selectedItem: Int? = nil
    @State private var lastSelectedItem: Int? = nil
    @Namespace private var ns

    var body: some View {
        LazyVGrid(columns: columns, spacing: 20) {

            // Placeholders for tiles
            ForEach(0..<colors.count, id: \.self) { index in
                Color.clear
                    .frame(width: 150, height: 200)
                    .matchedGeometryEffect(id: index, in: ns)
            }
        }
        .padding()
        .background {

            // Placeholder for the centered item
            Color.clear
                .frame(width: 150, height: 200)
                .matchedGeometryEffect(id: centeredItemId, in: ns)
        }
        .overlay {
            ZStack {

                // The visible items
                ForEach(Array(colors.enumerated()), id: \.offset) { index, color in
                    let isSelected = selectedItem == index

                    RoundedRectangle(cornerRadius: 12)
                        .fill(color)
                        .shadow(radius: isSelected ? 10 : 0)
                        .zIndex(isSelected ? 2 : (lastSelectedItem == index ? 1 : 0))
                        .matchedGeometryEffect(id: isSelected ? centeredItemId : index, in: ns, isSource: false)
                        .onTapGesture {
                            withAnimation(.spring(response: 0.5, dampingFraction: 0.7)) {
                                if isSelected {
                                    selectedItem = nil
                                } else {
                                    selectedItem = index
                                }
                            } completion: {
                                lastSelectedItem = selectedItem
                            }
                        }
                }
            }
        }
    }
}

本文标签: iosCant change zIndex of LazyVGrid element to bring it to the front with animationStack Overflow