このStanford Universityの授業が参考になりました。
UICollectionViewのCellをDrag & Dropする場合は、専用のAPIが用意されています。UITableViewにもほぼ同じAPIがあります。
まず、collectionView.dragDelegate = selfとすることで、ViewControllerをDelegateに設定することが出来ます。
dragDelegateで重要なのはitemsForBeggingメソッドです。また、dragSessionWillBeginメソッドとdragSessionDidEndメソッドでdragSessionの状態をトラッキング出来るので、それを利用してisDraggingプロパティを更新しています。
extension ViewController: UICollectionViewDragDelegate {
// modifying the isDragging state according to the dragSession lifecycle
func collectionView(_ collectionView: UICollectionView, dragSessionWillBegin session: UIDragSession) {
isDragging = true
}
func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession) {
isDragging = false
}
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
// in order for session to know the localContext. This will be used in dropSessionDidUpdate method
session.localContext = collectionView
let itemProvider = NSItemProvider(object: colors_A[indexPath.item])
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = indexPath.item
return [dragItem]
}
}
DropDelegateで重要なのは、そのドラッグが開始されたのがそのCollectionViewの中か外かを判別し、中であればDropProposalのoperationを.moveに、外であれば.copyを設定することが出来ます。これにより、同一CollectionView内のドラッグの場合は順番入替(reorder)を、外からのドラッグの場合は新規挿入を、と実装を分けることが可能になります。
また、自身以外のUIViewをドラッグ先(DragDestination)とする場合は以下のように実装します。
class ViewController: UIViewController {
lazy var trashImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(systemName: "trash"))
let dropInteraction = UIDropInteraction(delegate: self)
imageView.addInteraction(dropInteraction)
// isUserInteractionEnabled needs to be set to true, otherwise it will not work...
imageView.isUserInteractionEnabled = true
imageView.alpha = 0
imageView.isHidden = !isDragging
return imageView
}()
}
extension ViewController: UIDropInteractionDelegate {
func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
return session.canLoadObjects(ofClass: UIColor.self)
}
func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
return UIDropProposal(operation: .copy)
}
func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
session.items.forEach({ item in
if let index = item.localObject as? Int {
// performBatchUpdates is necessary also to make the changes animate
collectionView_A.performBatchUpdates {
colors_A.remove(at: index)
collectionView_A.deleteItems(at: [IndexPath(item: index, section: 0)])
}
}
})
}
}