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