Search code examples
iosswiftuikittableviewhaptic-feedback

how do I add a haptic feedback in a table view while rearranging it’s cells


I need to add haptic feedback in a table view when the user tries to rearrange the cells via dragging and dropping them, but whenever they pass through a cell, even if they don’t drop it there, it still needs to generate a haptic. please help. Thanks in advance.

I tried overriding various tableView methods, and did get an haptic on lifting the cell and while dropping it. I need something that whenever the highlighted item passes through a cell, even if the user doesn't drop it there, it still needs to generate a haptic, which is where I’m stuck.


Solution

  • UITableViewDropDelegate has a method that can inform you while the drag is taking place:

    tableView(_:dropSessionDidUpdate:withDestinationIndexPath:)
    

    From Apple's docs:

    Discussion

    While the user is dragging content, the table view calls this method repeatedly to determine how you would handle the drop if it occurred at the specified location. The table view provides visual feedback to the user based on your proposal.

    In your implementation of this method, create a UITableViewDropProposal object and use it to convey your intentions. Because this method is called repeatedly while the user drags over the table view, your implementation should return as quickly as possible.

    So, we could add an IndexPath "tracking" property to our controller:

    // a "tracking" property for drag update
    var currentDestinationIndexPath: IndexPath?
    

    then, assuming we've setup a UIImpactFeedbackGenerator (I called mine dropTargetChangedHapticGenerator), implement something like this in UITableViewDropDelegate:

    func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
        var dropProposal = UITableViewDropProposal(operation: .cancel)
        
        // Accept only one drag item.
        guard session.items.count == 1 else { return dropProposal }
    
        // destinationIndexPath is optional, so unwrap it
        if let destIDX = destinationIndexPath {
            
            // our currentDestinationIndexPath is optional, so
            //  if it's nil, assign it
            if currentDestinationIndexPath == nil {
                currentDestinationIndexPath = destIDX
            }
            
            // unwrap it
            if let currIDX = currentDestinationIndexPath {
                
                // if currIDX != destIDX we've dragged to a new insertion point
                if currIDX != destIDX {
                    // update our "tracking" property
                    currentDestinationIndexPath = destIDX
                    
                    // provide haptic feedback
                    print("haptic", destIDX)
                    dropTargetChangedHapticGenerator.impactOccurred()
                }
            }
            
        }
    
        // The .move drag operation is available only for dragging within this app and while in edit mode.
        if tableView.hasActiveDrag {
            if tableView.isEditing {
                dropProposal = UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
            }
        } else {
            // Drag is coming from outside the app.
            dropProposal = UITableViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath)
        }
    
        return dropProposal
    }
    

    Probably also want to set currentDestinationIndexPath = nil after the drop has completed.

    Note: I based this on the sample app available from Apple here: Adopting Drag and Drop in a Table View