2

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?

JKaz
  • 765
  • 6
  • 18
  • Does the same thing happen when you just do`return []`? – Orkhan Alikhanov Aug 25 '17 at 20:38
  • Yes it does for a pure `return []` – JKaz Aug 25 '17 at 21:09
  • Do you allow editing for the tableview, i.e. the old way to reorder rows? – Losiowaty Aug 25 '17 at 21:25
  • 1
    @Losiowaty, you led to the answer. If you wanna post it, I'll let you and accept it. If not, I'll post it. It has to do with canMoveRowAt, which I'd done right for 10.x, but there's a little twist for 11.x. Thanks for the question you asked! – JKaz Aug 25 '17 at 21:44
  • @JKaz go ahead and post it, I’d be happy to upvote it :) All I did was give you a nudge in the right direction. – Losiowaty Aug 25 '17 at 22:21

1 Answers1

4

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.

JKaz
  • 765
  • 6
  • 18
  • For UICollectionView from iOS14 and above, the equivalent is to implement reorderingHandlers.canReorderItem to return false – glotcha Aug 18 '22 at 01:09