Why does empty UIDragItem array still allow row to be dragged?

477 Views Asked by At

Implementing iOS 11 Drag and Drop in a table view. If I don't want the first row to be dragged, I assume I return an empty array from tableView(:itemsForBeginning:)

func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
    if indexPath.row == 0 {
        return []
    }
    let dragItems = self.dragItems(forRowAt: indexPath)
    print("dragging row index \(String(describing: dragItems.first?.localObject as? Int))")
    return dragItems
}

Return an empty array when you do not allow the user to drag content from the specified index path.

But even when it's verified that [ ] is returned, the drag still happens. That means either I screwed up or the feature is not implemented as documented. I'm always hesitant to think it's someone else, so my question is if the return of [ ] as implemented should in fact prevent the drag of the 0 row? Anyone else verify this or show it as working as documented?

Thanks

Edit: Sample code from WWDC video includes this chunk:

    if tableView.isEditing {
        // User wants to reorder a row, don't return any drag items. The table view will allow a drag to begin for reordering only.
        return []
    }

Is this saying that if you don't return any drag items, the table view will still allow dragging?!?! Then how would one prevent a row from being dragged at all?

1

There are 1 best solutions below

1
On BEST ANSWER

Thanks to @Losiowaty for pointing in useful direction.

I knew that in tableView, the drop delegate would look to tableView(:moveRowAt:) if there was just one UIDragItem. What I didn't see documented anywhere was that it also checked with tableView(:canMoveRowAt:), though that now seems obvious in retrospect.

My canMoveRowAt looked like this:

// Override to support conditional rearranging of the table view.
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
    // Return false if you do not want the item to be re-orderable. If coded that the first row is non-editable, that row is by definition also non-re-orderable
    return true
}

Note the comment inside the method. I don't know if I wrote the comment or copied it from somewhere. I'd prevented row 0 from being editable (deletable and reorder-able in edit mode) and let that override canMoveRowAt, but that is ignored with iOS 11 drag and drop apparently. So the solution is to be explicit, as in:

// Override to support conditional rearranging of the table view.
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
    // Return false if you do not want the item to be re-orderable.
    if indexPath.row == 0 {return false}
    return true
}

An additional complexity in diagnosing this is that the same code in an iMessage app on iPad is not reaching tableView(:moveRowAt:), but is reaching there on iPhone. For an iOS app, tableView(:moveRowAt:) is reached on both iPad and iPhone, though this may be separate issue.