[Swift]UITableViewのdropキャンセル方法

Swift

はじめに

drop位置によってdropをキャンセル方法になります。
サンプルのgifでは、サブタスクを持ったタスク(タスク4)を別のサブタスクを持ったタスク(タスク2)のサブタスクにドロップをした場合にキャンセルしています。

環境

Xcode: 13.4.1
Swift: 5.6.1
iOS: 15.4

前提

UITableViewDropDelegate, UITableViewDragDelegateを使用するので、delegateの設定が必要です。

tableView.dragDelegate = self
tableView.dropDelegate = self

実装方法

    private var selectedDragRowIndex = 0

    func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        let viewModel = tasksViewModel.getTaskTableViewCellViewModel(index: indexPath.row)

        if viewModel.isShowedSubTasks {
            // 親タスクで、サブタスクを展開している場合サブタスクを閉じる
            tasksViewModel.closeSubTasks(viewModel: viewModel)
        }
        // drop制御するためにdragしたセルのインデックスを保持
        selectedDragRowIndex = indexPath.row
        let dragItem = UIDragItem(itemProvider: NSItemProvider())
        dragItem.localObject = tasksViewModel.taskTableViewCellViewModelArray[indexPath.row]
        return [dragItem]
    }

    func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {

        guard
            let originRow = destinationIndexPath?.row,
                originRow < tasksViewModel.taskTableViewCellViewModelArray.count
        else {
            // データ数以上のインデックスはキャンセル
            return UITableViewDropProposal(operation: .cancel)
        }

        // dragした行のTaskデータを取得
        let fromViewModel = tasksViewModel.getTaskTableViewCellViewModel(index: selectedDragRowIndex)
        // drag行のインデックス > drop行のインデックスの場合(dragして上に移動してdropした場合)
        // destinationIndexPathはdrop行の下の行のインデックスが取得するので、
        // 下から移動して来た場合は-1した行のTaskデータを取得
        let toRow = (originRow == 0) ? 0 : (originRow < selectedDragRowIndex) ? originRow - 1 : originRow
        let toViewModel = tasksViewModel.getTaskTableViewCellViewModel(index: toRow)

        if !fromViewModel.hasSubTasks {
            // dragしたTaskにサブタスクがなければ、どこでもdrop可能
            return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
        }

        if toViewModel.hasSubTasks || !toViewModel.parentId.isEmpty {
            // drop行の1つ上のTaskが以下条件の場合キャンセル
            // 親タスクでサブタスクを持っている場合(hasSubTasks==true)
            // サブタスク(parentId==blank)の場合
            return UITableViewDropProposal(operation: .cancel)
        }
        return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
    }

解説

itemsForBeginningメソッドでdrag開始時にselectedDragRowIndexにインデックスを保持しています。
これはdropの際に、Taskデータを取得してキャンセル判定するためです。

dropSessionDidUpdateメソッドでdropの判定処理をしています。
注意点としては、destinationIndexPathのrowは上->下の移動の場合、drop位置の1つ上のインデックスを返すが、下->上の移動の場合drop位置の1つ下のインデックスを返す点です。

参考

collectionView(_:itemsForBeginning:at:) | Apple Developer Documentation
Provides the initial set of items (if any) to drag.
collectionView(_:dropSessionDidUpdate:withDestinationIndexPath:) | Apple Developer Documentation
Tells your delegate that the position of the dragged data over the collection view changed.

コメント

タイトルとURLをコピーしました