iosswiftswiftuiz-indexlazyhgrid

Incorrect zindex calculation when dragging


I'm trying to drag a group of elements and I want the element to be on top of all other elements in the group when I drag it. I'm trying to change the zIndex of the dragged element but for some reason it doesn't work... Maybe someone who knows this stuff can point out my mistake

struct DragShapesView: View {
    
    var body: some View {
        let layout = [GridItem(), GridItem()]
        
        LazyHGrid(rows: layout, spacing: 15) {
            ForEach(1..<5) { i in
                Circle()
                    .frame(width: 50, height: 50)
                    .overlay {
                        Text(String(i))
                            .font(.evolventaBold(25))
                            .foregroundColor(.white)
                    }
                    .draggable(
                        onChanged: { location in
                            
                        },
                        onEnded: {
                            
                        }
                    )
            }
        }
    }
}

modifier draggable is:

struct Draggable: ViewModifier {
    
    @State private var location: CGPoint = .zero
    @State private var offset: CGSize = .zero
    @State private var zIndex: Double = 0
    
    var onChanged: ((CGPoint) -> Void)?
    var onEnded: (() -> Void)?
    
    func body(content: Content) -> some View {
        content
            .zIndex(zIndex)
            .offset(offset)
            .gesture(
                DragGesture(coordinateSpace: .global)
                    .onChanged { value in
                        zIndex = Double.infinity
                        offset = value.translation
                        onChanged?(value.location)
                    }
                    .onEnded { _ in
                        zIndex = .zero
                        offset = .zero
                        onEnded?()
                    }
            )
    }
}

extension View {
    
    func draggable(onChanged: ((CGPoint) -> Void)?, onEnded: (() -> Void)?) -> some View {
        self.modifier(Draggable(onChanged: onChanged, onEnded: onEnded))
    }
}

Solution

  • Unfortunately, changing the zIndex of items in a lazy grid does not work well.

    To use .matchedGeometryEffect in your case, the following changes are needed:

    Here is an updated version of the example to show it working:

    struct DragShapesView: View {
        private let colors: [Color] = [.red, .orange, .yellow, .green]
        private let layout = [GridItem(), GridItem()]
        @Namespace private var ns
    
        var body: some View {
            LazyHGrid(rows: layout, spacing: 15) {
                ForEach(Array(colors.enumerated()), id: \.offset) { i, _ in
                    Color.clear
                        .frame(width: 100, height: 100)
                        .matchedGeometryEffect(id: i, in: ns)
                }
            }
            .overlay {
                ForEach(Array(colors.enumerated()), id: \.offset) { i, color in
                    Circle()
                        .fill(color)
                        .overlay {
                            Text(String(i))
                                .font(.title) // .evolventaBold(25)
                                .foregroundStyle(.white)
                        }
                        .draggable(
                            onChanged: { location in },
                            onEnded: {}
                        )
                        .matchedGeometryEffect(id: i, in: ns, isSource: false)
                }
            }
        }
    }
    

    Animation