UICollectionView drag and drop while resizing cell

700 views Asked by At

I want to implement drag and drop feature with UICollectionViewDragDelegate, UICollectionViewDropDelegate it works pretty well but I need to shrink the underneath cells while lifting one cell with drag behavior and shrink back to original size when user drop the cell back to the cells. But somehow I can't get a stable result with the code like below. The animation is weird and the behavior because unpredictable when trying to drag the cell on the edge of the screen. How can I make the result more predictable?

import UIKit

class ViewController: UIViewController {
    @IBOutlet var collectionView: UICollectionView! {
        didSet {
            collectionView.delegate = self
            collectionView.dataSource = self
            collectionView.dragDelegate = self
            collectionView.dropDelegate = self
            collectionView.dragInteractionEnabled = true
        }
    }

    var items: [UIColor] = [.green, .blue, .brown, .cyan, .red, .magenta, .yellow, .systemPink]

    var isShrink: Bool = false {
        didSet {
            //collectionView.collectionViewLayout.invalidateLayout()
            collectionView.performBatchUpdates({}, completion: { _ in })
        }
    }
}

extension ViewController: UIGestureRecognizerDelegate {}

extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView,
                        layout _: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize {
        isShrink ? CGSize(width: 60, height: 60) : CGSize(width: 80, height: 60)
    }
}

class Cell: UICollectionViewCell {
    var shrink: ((Bool) -> Void)?
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        let gesture = UILongPressGestureRecognizer(target: self, action: #selector(longPressed(gesture:)))
        addGestureRecognizer(gesture)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        //shrink?(true)
        print("touchesBegan")
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        print("touchesEnd")
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    @objc func longPressed(gesture: UILongPressGestureRecognizer) {
        switch gesture.state {
        case .began:
            print("began")
        case .ended:
            print("ended")
        default:
            print("default")
        }
    }

//    weak var coordinator: DragCoordinator?
//
    override func dragStateDidChange(_ dragState: UICollectionViewCell.DragState) {
        super.dragStateDidChange(dragState)

        switch dragState {
        case .dragging:
            shrink?(true)
        case .lifting:
            shrink?(true)
        case .none:
            shrink?(false)
        @unknown default:
            fatalError()
        }
    }
}


extension ViewController: UICollectionViewDataSource {
    func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int {
        items.count
    }

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

        cell.shrink = { [weak self] isShrink in
            self?.isShrink = isShrink
        }
        cell.backgroundColor = items[indexPath.row]

        return cell
    }
}

extension ViewController: UICollectionViewDragDelegate {
    func collectionView(_ cv: UICollectionView,
                        itemsForBeginning _: UIDragSession,
                        at indexPath: IndexPath) -> [UIDragItem] {
        [.init(itemProvider: .init(object: "\(indexPath.row)" as NSString))]
    }
}

extension ViewController: UICollectionViewDropDelegate {
    func collectionView(_ collectionView: UICollectionView,
                        performDropWith coordinator: UICollectionViewDropCoordinator) {
        guard let destinationIndexPath = coordinator.destinationIndexPath else {
            return
        }

        let item = coordinator.items[0]
        switch coordinator.proposal.operation {
        case .move:
            if let sourceIndexPath = item.sourceIndexPath {
                collectionView.performBatchUpdates({
                    let item = items[sourceIndexPath.row]
                    items.remove(at: sourceIndexPath.row)
                    items.insert(item, at: destinationIndexPath.row)
                    collectionView.deleteItems(at: [sourceIndexPath])
                    collectionView.insertItems(at: [destinationIndexPath])
                })
            }

            coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)

        default:
            return
        }
    }

    func collectionView(_: UICollectionView,
                        dropSessionDidUpdate session: UIDropSession,
                        withDestinationIndexPath _: IndexPath?) -> UICollectionViewDropProposal {
        guard session.localDragSession != nil else {
            return .init(operation: .copy, intent: .insertAtDestinationIndexPath)
        }

        guard session.items.count == 1 else {
            return .init(operation: .cancel)
        }

        return .init(operation: .move, intent: .insertAtDestinationIndexPath)
    }
}


0

There are 0 answers