Search code examples
swiftuiswiftui-animation

SwiftUI Draggable Dropdestination animation


I'm trying to use draggable in combination with dropDestination to move around cells in a grid. Everything works except the animation in between drags.

struct PhotosGridViewDraggable: View {
    
    @State var draggedItem: Color?
    
    @State var colors: [Color] = [.red, .blue, .gray, .green, .pink, .yellow]
    
    @Namespace var animation
    
    var body: some View {
        GeometryReader { proxy in
            let hGap: CGFloat = 14
            let width = proxy.size.width - (hGap * 2)
            let cellWidth = (width / 3)
            let cellSize = CGSize(width: cellWidth, height: cellWidth * 1.5)
            
            let columns = [GridItem(.fixed(cellWidth)), GridItem(.fixed(cellWidth)), GridItem(.fixed(cellWidth))]
            
            LazyVGrid(columns: columns) {
                ForEach(colors.indices, id: \.self) { index in
                    let color = colors[index]
                    Rectangle()
                        .fill(color)
                        .frame(width: cellSize.width, height: cellSize.height)
                        .draggable(color, preview: {
                            RoundedRectangle(cornerRadius: 10)
                                .fill(.ultraThinMaterial)
                                .frame(width: 1, height: 1)
                                .onAppear {
                                    draggedItem = color
                                }
                        })
                        .dropDestination(for: Color.self, action: { items, location in
                            return false
                        }, isTargeted: { status in
                            if let draggedItem = draggedItem, status, draggedItem != color {
                                if let sourceIndex = colors.firstIndex(of: draggedItem), let destinationIndex = colors.firstIndex(of: color) {
                                    withAnimation { // PROBLEM: does nothing
                                        let sourceItem = colors.remove(at: sourceIndex)
                                        colors.insert(sourceItem, at: destinationIndex)
                                    }
                                }
                            }
                            
                        })
                }
            }
            
        }
    }
    
}

Expected:

enter image description here

Actual:

enter image description here


Solution

  • The id argument is configured to use the key .element, which means that the view uses the value of each element as a unique identifier for the view's elements. This means that if the elements in the colors array are unique, this will work correctly.

    struct PhotosGridViewDraggable: View {
    
    @State var draggedItem: Color?
    
    @State var colors: [Color] = [.red, .blue, .gray, .green, .pink, .yellow]
    
    @Namespace var animation
    
    var body: some View {
        GeometryReader { proxy in
            let hGap: CGFloat = 14
            let width = proxy.size.width - (hGap * 2)
            let cellWidth = (width / 3)
            let cellSize = CGSize(width: cellWidth, height: cellWidth * 1.5)
            
            let columns = [GridItem(.fixed(cellWidth)), GridItem(.fixed(cellWidth)), GridItem(.fixed(cellWidth))]
            
            LazyVGrid(columns: columns) {
                ForEach(Array(colors.enumerated()), id: \.element) { index, color in
                 
                    Rectangle()
                        .fill(color)
                        .frame(width: cellSize.width, height: cellSize.height)
                        .draggable(color, preview: {
                            RoundedRectangle(cornerRadius: 10)
                                .fill(.ultraThinMaterial)
                                .frame(width: 1, height: 1)
                                .onAppear {
                                    draggedItem = color
                                }
                        })
                        .dropDestination(for: Color.self, action: { items, location in
                            return false
                        }, isTargeted: { status in
                            if let draggedItem = draggedItem, status, draggedItem != color {
                                if let sourceIndex = colors.firstIndex(of: draggedItem), let destinationIndex = colors.firstIndex(of: color) {
                                    withAnimation { 
                                        let sourceItem = colors.remove(at: sourceIndex)
                                        colors.insert(sourceItem, at: destinationIndex)
                                    }
                                }
                            }
                            
                        })
                }
            }
            
        }
    }
    
    }