Search code examples
iosswiftuitableviewdrag-and-dropuikit

UITableViewCell drag and drop starts with a Forbidden icon


So, I've been wanting to implement the longpress gesture to re-order cells in a UITableView.

However, I noticed that as soon as I longpress a cell and shift it slightly in any direction, there is a Forbidden grey icon in the top-right corner of the cell. As I shift the cell, the forbidden icon remains until the delegate method tableView(_ tableView:, dropSessionDidUpdate:, withDestinationIndexPath:) gets called. Then the forbidden icon disappears, and doesn't come back (not even after re-entering the original cell location).

If I completely remove the protocol UITableViewDropDelegate from the class declaration (as it appears to not be needed for this whole process to work), the only thing that's different is that the Forbidden icon comes back when re-entering the original cell position. Weird?

I feel like this is not normal, since dropping the cell in the same spot is definitely NOT forbidden (and releasing the longpress allows the cell to return to its position). I found a similar question from last year, unfortunately it got no answers.

Anyway, this is the code I've got so far:

//in viewDidLoad(). Also, class declaration includes protocols UITableViewDragDelegate and UITableViewDropDelegate
tableView.dragInteractionEnabled = true
tableView.dragDelegate = self
tableView.dropDelegate = self

//I left this blank as it prevents all cells from being allowed to be dragged/dropped; leaving it blank seem to force the tableView(_:, canMoveRowAt:) method to be called first and therefore only cells at specified indexPaths are allowed to move
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
    //original code
    //let dragItem = UIDragItem(itemProvider: NSItemProvider())
    //dragItem.localObject = myData[indexPath.row]
    //return [dragItem]

    //new code
    return []
}

func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
    if session.localDragSession != nil, tableView.hasActiveDrag, session.items.count == 1
    {
        return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
    }

    return UITableViewDropProposal(operation: .cancel) //never gets called in tests, which seems expected given the 3 conditions above
}

//NEVER EVEN GETS CALLED (weird?), so I removed it
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
}

func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
    //my logic
}

func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
    //my logic
}

func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    //my logic
}

On a side note, sometimes after re-ordering a cell, the cell becomes invisible until it goes off-screen (and I assume its tableView(_ tableView:, cellForRowAt:) method gets called). I have a few ideas on how to solve this, namely reloading the cell or tableview after a successful move, but I still felt like this shouldn't happen.

Thanks, any help is greatly appreciated!


Solution

  • Turns out that updating both XCode (to 14.2) and iOS (to 16.3.1), fixed the forbidden icon issue.

    I did also learn that, for my use case at least, anything UITableViewDropDelegate-related was not needed to achieve simple longpress-to-reorder functionality, and that having the drag delegate's function return the empty array is what makes it work this way.

    I have a feeling, however, that the old "reorder" functionality is going to be deprecated at some point in the future (meaning this mix of old functions + the new drag delegate is not going to work) in favor of the whole drag + drop delegates working together.