はじめに
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.
コメント