Search code examples
swiftuidrag-and-drophaptic-feedback

Add haptic to drag and drop (draggable, dropDestination)


How can I add haptic effect on a drag and drop ? I follow this tutorial: https://www.youtube.com/watch?v=UFiOCcm6zTo but I can't figure out how to add haptic when we select a view and drop it.

thanks

LazyVGrid(columns: columns, spacing: 10, content: {
    ForEach(cards) { card in
        
        CardView(numberOfColumns: CGFloat(numberOfColumns), is2ColumnsSelected: is2ColumnsSelected, size: size, card: card)
            .draggable(card) {
                CardView(numberOfColumns: CGFloat(numberOfColumns), is2ColumnsSelected: is2ColumnsSelected, size: size, card: card)
                    .frame(width: size.width/CGFloat(numberOfColumns) - 20)
                    .onAppear {
                        draggingCard = card
                    }
            }
            .dropDestination(for: Card.self) { items, location in
                draggingCard = nil
                return false
            } isTargeted: { status in
                if let draggingCard, status, draggingCard != card {
                    if let sourceIndex = cards.firstIndex(of: draggingCard), let destinationIndex = cards.firstIndex(of: card) {
                        withAnimation(.bouncy) {
                            let sourceCard = cards.remove(at: sourceIndex)
                            cards.insert(sourceCard, at: destinationIndex)
                        }
                    }
                }
            }
    }
}
    

I tried to ad haptic in the draggable modifier and dropDestination modifier

CardView(numberOfColumns: CGFloat(numberOfColumns), is2ColumnsSelected: is2ColumnsSelected, size: size, card: card)
    .draggable(card) {
        UIImpactFeedbackGenerator(style: .medium).impactOccurred()
        CardView(numberOfColumns: CGFloat(numberOfColumns), is2ColumnsSelected: is2ColumnsSelected, size: size, card: card)
            .frame(width: size.width / CGFloat(numberOfColumns) - 20)
            .onAppear {
                draggingCard = card
            }
    }
    .dropDestination(for: Card.self) { _, _ in
        draggingCard = nil
        return false
    } isTargeted: { status in
        if let draggingCard, status, draggingCard != card {
            if let sourceIndex = cards.firstIndex(of: draggingCard), let destinationIndex = cards.firstIndex(of: card) {
                withAnimation(.bouncy) {
                    let sourceCard = cards.remove(at: sourceIndex)
                    cards.insert(sourceCard, at: destinationIndex)
                }
            }
        }
    }

Solution

  • Use the sensoryFeedback(_:trigger:) modifier to add haptics. You just need to declare a new @State as the trigger:, so that whenever the state changes, the haptics gets triggered.

    // suppose you want different haptics for when the user starts dragging
    // and for when a reorder of the items happens due to the drag
    @State var dragTrigger = false
    @State var reorderTrigger = false
    
    // let's also suppose that we have a grid of Colors
    @State var colors = [Color.red, .green, .blue, .purple, .yellow, .orange, .pink, .brown]
    @State var draggedItem: Color?
    

    Then, all you need to do is to insert dragTrigger.toggle() and reorderTrigger.toggle() at appropriate places, and add the sensoryFeedback

    RoundedRectangle(cornerRadius: 10)
        .fill(color)
        .draggable(color) {
            RoundedRectangle(cornerRadius: 10)
                .fill(.ultraThinMaterial)
                .frame(width: geo.size.width, height: geo.size.height)
                .onAppear {
                    dragTrigger.toggle()
                    draggedItem = color
                }
        }
        .dropDestination(for: Color.self) { items, location in
            draggedItem = nil
            return false
        } isTargeted: { status in
            if let draggedItem, status, draggedItem != color {
                if let sourceIndex = colors.firstIndex(of: draggedItem), let destinationIndex = colors.firstIndex(of: color) {
                    reorderTrigger.toggle()
                    withAnimation(.bouncy) {
                        let sourceItem = colors.remove(at: sourceIndex)
                        colors.insert(sourceItem, at: destinationIndex)
                    }
                }
            }
        }
    
    // add these to the LazyVGrid
    .sensoryFeedback(.selection, trigger: dragTrigger)
    .sensoryFeedback(.impact, trigger: reorderTrigger)