John Doe
John Doe

Reputation: 2491

How to prevent appending data to an existing cell on drag drop of table view cell?

I am dragging and dropping a table view cell. But it shows a plus icon when on top of a cell and if I drop, the text gets appended. How to prevent this plus icon and append behaviour and just allow to reorder?

extension CheckListTableViewController: UITableViewDragDelegate {

    func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        if let item = self.boardMan.getCheckListItem(indexPath: indexPath) {
            if let stringData = item.title.data(using: .utf8) {
                let itemProvider = NSItemProvider(item: stringData as NSData, typeIdentifier: kUTTypePlainText as String)
                let dragItem = UIDragItem(itemProvider: itemProvider)
                dragItem.localObject = (item, indexPath, tableView)
                return [dragItem]
            }
        }
        return []
    }
extension CheckListTableViewController: UITableViewDropDelegate {

    func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
        if coordinator.session.hasItemsConforming(toTypeIdentifiers: [kUTTypePlainText as String]) {
            coordinator.session.loadObjects(ofClass: NSString.self) { (items) in
                switch (coordinator.items.first?.sourceIndexPath, coordinator.destinationIndexPath) {
                case (.some(let sourceIndexPath), .some(let destinationIndexPath)):
                    Log.debug("src: \(sourceIndexPath) dest: \(destinationIndexPath)")
                    let updatedIndexPaths: [IndexPath]
                    if sourceIndexPath.row < destinationIndexPath.row {
                        updatedIndexPaths =  (sourceIndexPath.row...destinationIndexPath.row).map { IndexPath(row: $0, section: 0) }
                    } else if sourceIndexPath.row > destinationIndexPath.row {
                        updatedIndexPaths =  (destinationIndexPath.row...sourceIndexPath.row).map { IndexPath(row: $0, section: 0) }
                    } else {
                        updatedIndexPaths = []
                    }
                    if let checkListItem = self.utils.getCheckListItem(indexPath: sourceIndexPath) {
                        self.tableView.beginUpdates()
                        _ = self.utils.removeCheckListItem(indexPath: sourceIndexPath)
                        _ = self.utils.insertCheckListItem(checkListItem, indexPath: destinationIndexPath)
                        self.tableView.reloadRows(at: updatedIndexPaths, with: .none)
                        self.tableView.endUpdates()
                    }
                default:
                    Log.debug("default case")
                }
            }
        }
    }

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

The table view has drag drop delegates set and has swipe guestures added.

self.tableView.dragDelegate = self
self.tableView.dropDelegate = self
self.tableView.dragInteractionEnabled = true

Please see the screenshot below.

snap

Upvotes: 0

Views: 935

Answers (1)

rmaddy
rmaddy

Reputation: 318824

If you want to support drag and drop only for the purpose of reordering rows, then you don't need any of the code you posted.

Implement the normal "reorder" related methods of UITableViewDataSource and UITableViewDelegate. Set the table's dragDelegate and dropDelegate. You only need to implement one method each from UITableViewDragDelegate and UITableViewDropDelegate and their implementations are trivial.

For a simple one-section table view all you need is the following:

override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
    return true
}

override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    // Update your data model array to move the item at sourceIndexPath.row to destinationIndexPath.row
}

// MARK: - UITableViewDragDelegate

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

// MARK: - UITableViewDropDelegate

func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
    // no-op
}

Make sure you set the table view's dragDelegate and dropDelegate properties to self in viewDidLoad or in the storyboard. You also need to turn on the dragInteractionEnabled property.

override func viewDidLoad() {
    super.viewDidLoad()

    self.tableView.dragDelegate = self
    self.tableView.dropDelegate = self
    self.tableView.dragInteractionEnabled = true
}

That's all you need to support row reordering using drag and drop. Of course for complicated data models and multiple sections, you may need to also implement the targetIndexPathForMoveFromRowAt delegate method and your logic in moveRowAt may need further logic.


What's interesting is that the need to set the dropDelegate and to enable the dragInteractionEnabled is only needed for iPhones. The iPad (and Mac Catalyst) version does not need those set.

Upvotes: 2

Related Questions