简体   繁体   中英

Is it possible to use iOS 11 Drag and Drop to reorder multiple items/cells at a time in UITableView?

I know it's possible to reorder a single item/cell at a time when using the new UITableViewDropDelegate and UITableViewDragDelegate delegates but is it possible to support handling multiple.

For example in this screenshot I am holding a single item: 拖动单个项目/单元格

And dropping the cell puts it in to place.

However when I grab multiple cells I get the no entry sign and the cells wont reorder: 多选拖放,无输入符号

If I multiple items from another app it works fine, for example dragging multiple messages out of iMessage: 从 iMessage 中多选拖放

Is it possible to do this when reordering a table view with only local items so that you can reorder quicker?

Here is my code:

UITableViewDragDelegate

extension DotViewController: UITableViewDragDelegate {
    func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        return [getDragItem(forIndexPath: indexPath)]
    }

    func tableView(_ tableView: UITableView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem] {
        return [getDragItem(forIndexPath: indexPath)]
    }

    func getDragItem(forIndexPath indexPath: IndexPath) -> UIDragItem {
        // gets the item
    }
}

UITableViewDropDelegate

extension DotViewController: UITableViewDropDelegate {
    func tableView(_ tableView: UITableView, canHandle session: UIDropSession) -> Bool {
        return true
    }

    func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
        return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
    }

    func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
        // Handles Drop
    }
}

viewDidLoad

override func viewDidLoad() {
    super.viewDidLoad()

    tableView.dataSource = self
    tableView.delegate = self
    tableView.dragDelegate = self
    tableView.dropDelegate = self

    tableView.dragInteractionEnabled = true
}

It's possible to do multi-row reordering yourself by providing references to all the selected cells via your drag delegate (eg an array of section.row's ) then implementing tableView:performDropWith:withCoordinator to reorder them. (I suspect you know this)

If you want to support reordering by returning a drop proposal of UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) so UIKit uses the pre-iOS 11 tableView:moveRowAt:to function then this only supports a single row.

Table view moveRowAt IndexPath to IndexPath. You can continue to implement this, if you like, to support reordering, using Drag and Drop. Because table view is actually going to call this instead of calling through perform drop with coordinator, if you've returned that magic drop proposal and a single row is actually being reordered.

Source:https://developer.apple.com/videos/play/wwdc2017/223/?time=1836

I've tried the same with UICollectionView , then I found this:
link to tweet posted by Tyler Fox who was an active participant in the drag & drop development and you can also see him in the 2017-2018 WWDC videos related to the topic.

Tweet's content:

Table and collection view doesn't support UI for multi-item reordering, it's quite tricky to implement correctly.
You can use an intent of .unspecified and provide your own UI for it during the session though, and the drop will be accepted.
Please file an enhancement request if you'd like to see it supported with API. Any details on your specific use case are appreciated!

I'm afraid I cannot go into much detail here, but here's basically how I was able to get it to work:

  1. I extended an algorithm I found on StackOverflow to get a working function that I could use to reorder my datasource. Here's my possibly super-inefficient/inaccurate code for this:

Multiple element reordering in array (example is for ints but can be modified to use indexpaths and datasource type:

// Multiple element reordering in array
func reorderList(list: Array<Int>, draggedIndices: Array<Int>, targetIndex: Int) -> Array<Int> {

    // Defining the offsets that occur in destination and source indexes when we iterate over them one by one
    var array = list
    var draggedItemOffset = 0
    var targetIndexOffset = 0

    // Items being dragged ordered by their indexPaths
    let orderedDragIndices = draggedIndices.sorted() // Add {$0.row < $1.row for sorting IndexPaths}

    // Items being dragged ordered by their selection order
    var selectionOrderDragItems = [Int]()

    draggedIndices.forEach { (index) in
        selectionOrderDragItems.append(array[index])
    }

    // Reordering the list
    for i in (0...(orderedDragIndices.count - 1)).reversed()  {
        let removedItem = array.remove(at: orderedDragIndices[i] + draggedItemOffset)

        array.insert(removedItem, at: targetIndex + targetIndexOffset)

        if (i - 1 >= 0) && orderedDragIndices[i - 1] >= targetIndex {
            draggedItemOffset += 1
        } else {
            draggedItemOffset = 0
            targetIndexOffset -= 1
        }
    }

    // Right now the dropped items are in the order of their source indexPaths. Returning them back to the order of selection
    for index in Array(targetIndex + targetIndexOffset + 1...targetIndexOffset).sorted(by: >) {
        array.remove(at: index)
    }
    array.insert(contentsOf: selectionOrderDragItems, at: targetIndex + targetIndexOffset + 1)

    return array
}
  1. In the dragDelegate and dropDelegate methods, I used single item reordering (UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)) to get the free animation where the other cells make space for a drop. However, as the user tapped on additional rows, I collected these additional indexPaths using didSelectRow while the drag session was active.

  2. Using the above array of selected rows, in the performDrop delegate method, I used the algorithm described in (1) to restructure my datasource and reload the tableview section with animation.

  3. I did some additional animation to show the user that rows are being collected under the finger using panGestureRecognizer and making a snapshot of the cell upon selection.

  4. Note that I did not use canMoveRowAt or canEditRowAt or moveRowAt or other traditional methods in this process. I used the performDrop method from the iOS 11 APIs.

Hope it helps, and do reply if you find that the algorithm has some cases where it fails. Thanks and good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM