UICollectionViewDropDelegate: Drag cells on Delete Icon

1.8k views Asked by At

I have a collectionview and will support the drag and drop functionality of iOS 11. One requirement is that cells need to be removed by dragging them on a garbage bin on the bottom of the view. Is there a another possibility then using a second collectionview that holds that delete symobol?

Unfortunately a UIView can't be a UICollectionViewDropDelegate.

1

There are 1 answers

4
netshark1000 On BEST ANSWER

Best solution so far is to put an invisible collectionview above the delete icon. Here is my code:

        import UIKit

    class DragDropViewController: UIViewController
    {
        private var items1 = [String]()
        //MARK: Outlets
        @IBOutlet weak var collectionView1: UICollectionView!
        @IBOutlet weak var collectionView2: UICollectionView!
        @IBOutlet weak var trashImage: UIImageView!



        private func createData(){
            for index in 1...130{
                items1.append("\(index)")
            }
        }

        private func indexForIdentifier(identifier: String)->Int?{
            return items1.firstIndex(of: identifier)
        }

        //MARK: View Lifecycle Methods
        override func viewDidLoad()
        {
            super.viewDidLoad()

            createData()
            trashImage.alpha = 0
            trashImage.layer.cornerRadius = 30

            self.collectionView1.dragInteractionEnabled = true
            self.collectionView1.dragDelegate = self
            self.collectionView1.dropDelegate = self
            self.collectionView2.dropDelegate = self
        }

        //MARK: Private Methods

        /// This method moves a cell from source indexPath to destination indexPath within the same collection view. It works for only 1 item. If multiple items selected, no reordering happens.
        ///
        /// - Parameters:
        ///   - coordinator: coordinator obtained from performDropWith: UICollectionViewDropDelegate method
        ///   - destinationIndexPath: indexpath of the collection view where the user drops the element
        ///   - collectionView: collectionView in which reordering needs to be done.
        private func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView)
        {
            let items = coordinator.items
            if items.count == 1, let item = items.first, let sourceIndexPath = item.sourceIndexPath
            {
                var dIndexPath = destinationIndexPath
                if dIndexPath.row >= collectionView.numberOfItems(inSection: 0)
                {
                    dIndexPath.row = collectionView.numberOfItems(inSection: 0) - 1
                }
                collectionView.performBatchUpdates({


                    self.items1.remove(at: sourceIndexPath.row)
                    self.items1.insert(item.dragItem.localObject as! String, at: dIndexPath.row)

                    collectionView.deleteItems(at: [sourceIndexPath])
                    collectionView.insertItems(at: [dIndexPath])
                })
                coordinator.drop(items.first!.dragItem, toItemAt: dIndexPath)
            }
        }

        /// This method copies a cell from source indexPath in 1st collection view to destination indexPath in 2nd collection view. It works for multiple items.
        ///
        /// - Parameters:
        ///   - coordinator: coordinator obtained from performDropWith: UICollectionViewDropDelegate method
        ///   - destinationIndexPath: indexpath of the collection view where the user drops the element
        ///   - collectionView: collectionView in which reordering needs to be done.
        private func removeItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView)
        {
            collectionView.performBatchUpdates({

                for item in coordinator.items
                {
                    guard let identifier = item.dragItem.localObject as? String else {
                        return
                    }

                    if let index = indexForIdentifier(identifier: identifier){
                        let indexPath = IndexPath(row: index, section: 0)
                        items1.remove(at: index)
                        collectionView1.deleteItems(at: [indexPath])
                    }
                }
            })
        }
    }

    // MARK: - UICollectionViewDataSource Methods
    extension DragDropViewController : UICollectionViewDataSource
    {
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
        {
            return collectionView == self.collectionView1 ? self.items1.count : 0
        }

        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
        {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell1", for: indexPath) as! DragDropCollectionViewCell

            cell.customLabel.text = self.items1[indexPath.row].capitalized

            return cell
        }
    }

    // MARK: - UICollectionViewDragDelegate Methods
    extension DragDropViewController : UICollectionViewDragDelegate
    {
        func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem]
        {
            let item = self.items1[indexPath.row]
            let itemProvider = NSItemProvider(object: item as NSString)
            let dragItem = UIDragItem(itemProvider: itemProvider)
            dragItem.localObject = item
            return [dragItem]
        }

        func collectionView(_ collectionView: UICollectionView, itemsForAddingTo session: UIDragSession, at indexPath: IndexPath, point: CGPoint) -> [UIDragItem]
        {
            let item = self.items1[indexPath.row]
            let itemProvider = NSItemProvider(object: item as NSString)
            let dragItem = UIDragItem(itemProvider: itemProvider)
            dragItem.localObject = item
            return [dragItem]
        }

    }

    // MARK: - UICollectionViewDropDelegate Methods
    extension DragDropViewController : UICollectionViewDropDelegate
    {
        func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -> Bool
        {
            return session.canLoadObjects(ofClass: NSString.self)
        }

        func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal
        {
            if collectionView === self.collectionView1
            {
                return collectionView.hasActiveDrag ?
                    UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath) :
                    UICollectionViewDropProposal(operation: .forbidden)
            }
            else
            {
                if collectionView.hasActiveDrag{
                    return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
                }


                for item in session.items
                {
                    guard let identifier = item.localObject as? String else {
                        return UICollectionViewDropProposal(operation: .forbidden)
                    }
//not every cell is allowed to be deleted
                    if Int(identifier)! % 3 == 0{
                        return UICollectionViewDropProposal(operation: .forbidden)
                    }
                }

                trashImage.backgroundColor = UIColor.red.withAlphaComponent(0.4)
                return  UICollectionViewDropProposal(operation: .move, intent: .unspecified)
            }
        }

        func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator)
        {
            let destinationIndexPath: IndexPath
            if let indexPath = coordinator.destinationIndexPath
            {
                destinationIndexPath = indexPath
            }
            else
            {
                // Get last index path of table view.
                let section = collectionView.numberOfSections - 1
                let row = collectionView.numberOfItems(inSection: section)
                destinationIndexPath = IndexPath(row: row, section: section)
            }

            if coordinator.proposal.operation == .move{
                if coordinator.proposal.intent == .insertAtDestinationIndexPath{
                    self.reorderItems(coordinator: coordinator, destinationIndexPath:destinationIndexPath, collectionView: collectionView)
                }
                else{

                    self.removeItems(coordinator: coordinator, destinationIndexPath: destinationIndexPath, collectionView: collectionView)
                }
            }
        }

        func collectionView(_ collectionView: UICollectionView, dropSessionDidExit session: UIDropSession) {
             trashImage.backgroundColor = UIColor.clear
        }

        func collectionView(_ collectionView: UICollectionView, dropSessionDidEnd session: UIDropSession) {
             trashImage.backgroundColor = UIColor.clear
        }
    }