On an app I'm working on, I've run into a problem where trying to reorder the cells in a collection view causes the app to crash with EXC_BAD_ACCESS. Below is a very simplified form of that logic I wrote to try and pin down the cause:
I have found that with this code, a crash will happen when you try reordering the cells. The crash will occur at self.collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: self.collectionView)) on line 49. I've noticed that setting flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize is what leads to the crash. When I don't set it, it does not crash (on iOS 13 it doesn't crash either way). In tinkering with it further, I've found that if I leave that variable set, but remove the sizeForItemAt delegate method, it will also not crash. While that seems to work as a fine solution here, we do make use of that delegate method in our main app. Does anyone know what is happening here or how to work around this?
I know that there are many other steps to properly implement self-sizing cells and that isn't performed here, but I've found that it doesn't prevent the crash here, as we perform them in our main app. From my understanding, not fully implementing self-sizing cells mostly should mostly lead to UI bugs anyways and not this full on crash, which seems to have popped up in iOS 14.
This is most recently tested with:
iOS 14 Developer Beta 6
Xcode 11.3.1
Code Block swift import UIKit class ViewController: UIViewController { var colors: [UIColor] = [.blue, .green, .red, .brown] @IBOutlet weak var collectionView: UICollectionView! @IBOutlet weak var flowLayout: UICollectionViewFlowLayout! override func viewDidLoad() { super.viewDidLoad() self.collectionView.delegate = self self.collectionView.dataSource = self self.collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell") let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongGesture(gesture:))) self.collectionView.addGestureRecognizer(longPressGesture) flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize self.collectionView.reloadData() } func reorderItems(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView) { if let item = coordinator.items.first, let sourceIndexPath = item.sourceIndexPath { collectionView.performBatchUpdates({ self.colors.remove(at: sourceIndexPath.item) self.colors.insert(item.dragItem.localObject as! UIColor, at: destinationIndexPath.item) collectionView.deleteItems(at: [sourceIndexPath]) collectionView.insertItems(at: [destinationIndexPath]) }, completion: nil) coordinator.drop(item.dragItem, toItemAt: destinationIndexPath) } } @objc func handleLongGesture(gesture: UILongPressGestureRecognizer) { switch gesture.state { case .began: guard let selectedIndexPath = self.collectionView.indexPathForItem(at: gesture.location(in: self.collectionView)) else { break } self.collectionView.beginInteractiveMovementForItem(at: selectedIndexPath) case .changed: self.collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: self.collectionView)) case .ended: self.collectionView.endInteractiveMovement() default: self.collectionView.cancelInteractiveMovement() } } } extension ViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: 50, height: 50) } } extension ViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return colors.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let theCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) theCell.backgroundColor = self.colors[indexPath.row] return theCell } func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { let item = self.colors.remove(at: sourceIndexPath.item) self.colors.insert(item, at: destinationIndexPath.item) } }
I have found that with this code, a crash will happen when you try reordering the cells. The crash will occur at self.collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: self.collectionView)) on line 49. I've noticed that setting flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize is what leads to the crash. When I don't set it, it does not crash (on iOS 13 it doesn't crash either way). In tinkering with it further, I've found that if I leave that variable set, but remove the sizeForItemAt delegate method, it will also not crash. While that seems to work as a fine solution here, we do make use of that delegate method in our main app. Does anyone know what is happening here or how to work around this?
I know that there are many other steps to properly implement self-sizing cells and that isn't performed here, but I've found that it doesn't prevent the crash here, as we perform them in our main app. From my understanding, not fully implementing self-sizing cells mostly should mostly lead to UI bugs anyways and not this full on crash, which seems to have popped up in iOS 14.
This is most recently tested with:
iOS 14 Developer Beta 6
Xcode 11.3.1