Search code examples
iosswiftuicollectionviewdrag-and-drop

Swift: Drag and drop cells within the same UICollectionViewController


I am building an app which enables user to drag a UIView onto another. Currently I have the implementation as a UIScrollView but that does not give me the best experience as to achieve an experience like Apple's drag and drop requires handling of many cases ( including edge cases).

I am curious if this can be done using a UICollectionView. I am looking to drag cells onto another cells after which the cells contents merge and the source cell is removed.

Any idea/ suggestion would be appreciated. I am not sure if adding what I have is neccessary but if needed I can certainly add the code which I have.


Solution

  • Yes it's possible with UICollectionView.

    Assign below delegates to UICollectionView:

    UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDragDelegate, UICollectionViewDropDelegate 
    

    Write below DataSource methods:

        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return YourDataSource.count
        }
        
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PDFViewCollectionViewCell.reuseIdentifier, for: indexPath) as! PDFViewCollectionViewCell
            return cell
        }
    

    then write delegate methods for dragging:

    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
            let item = "\(indexPath.item)"
            let itemProvider = NSItemProvider(object: NSString(string: "\(indexPath.item)"))
            let dragItem = UIDragItem(itemProvider: itemProvider)
            dragItem.localObject = item
            print("DRAG ITEM IN BEGIN: \(item)")
            return [dragItem]
        }
    
     func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) {
            let destinationIndexPath: IndexPath
    
            if let indexPath = coordinator.destinationIndexPath {
                destinationIndexPath = indexPath
            } else {
                let section = collectionView.numberOfSections - 1
                let row = collectionView.numberOfItems(inSection: section)
                destinationIndexPath = IndexPath(row: row, section: section)
            }
    
    
            if let item = coordinator.items.first,
               let sourceIndexPath = item.sourceIndexPath {
                collectionView.performBatchUpdates {
                    let objNeedToInsert = YourDataSource[Int(item.dragItem.localObject as! String)!]
                    self.YourDataSource.remove(at: sourceIndexPath.item)
                    self.YourDataSource.insert(objNeedToInsert, at: destinationIndexPath.item)
                    collectionView.deleteItems(at: [sourceIndexPath])
                    collectionView.insertItems(at: [destinationIndexPath])
    
                }
                coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
            }
    }
    
    func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
            if collectionView.hasActiveDrag {
                return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
            }
            return UICollectionViewDropProposal(operation: .forbidden)
        }
    

    Best of Luck... Happy Coding :)