カテゴリー
iOS Swift

UICollectionViewのDrag and Drop

このStanford Universityの授業が参考になりました。

Sample Code

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)])
        }
      }
    })
  }
}