Reputation: 873
On iOS 10.0, UICollectionView pre-fetches cells by default. This leads to cells that are prepared for being shown on screen, but are hidden. This question describes it really well.
The following code will successfully deselect an index path when its cell is either visible or does not exist at all. If the cell exists and is hidden, the index path will be deselected, but the cell becomes stuck in the selected state until it is reused.
collectionView!.deselectItem(at: indexPath, animated: false)
This problem does not exits on iOS 9 or when pre-fetching is disabled with isPrefetchingEnabled = false
on iOS 10.0.
Is this a bug in UICollectionView or am I misunderstanding how deselectItem is supposed to work?
Here is the full code of a UICollectionViewController subclass that demonstrates this behaviour with the following steps:
import UIKit
private let reuseIdentifier = "Cell"
class CollectionViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
let button = UIButton(frame: CGRect(x: 10, y: 30, width: 360, height: 44))
button.backgroundColor = #colorLiteral(red: 0.9686274529, green: 0.78039217, blue: 0.3450980484, alpha: 1)
button.setTitleColor(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1), for: .normal)
button.setTitleColor(#colorLiteral(red: 0.05882352963, green: 0.180392161, blue: 0.2470588237, alpha: 1), for: .highlighted)
button.setTitle("Deselect Cell", for: .normal)
button.addTarget(self, action: #selector(CollectionViewController.buttonPress), for: .touchUpInside)
view.addSubview(button)
}
func buttonPress() {
for indexPath in collectionView!.indexPathsForSelectedItems ?? [IndexPath]() {
let cell = collectionView!.cellForItem(at: indexPath)
NSLog("Deselecting indexPath: %@, cell: %@", indexPath.description, cell?.frame.debugDescription ?? "not visible")
collectionView!.deselectItem(at: indexPath, animated: false)
}
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 300
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
cell.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
cell.selectedBackgroundView = UIView(frame: cell.bounds)
cell.selectedBackgroundView!.backgroundColor = #colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1)
return cell
}
}
Upvotes: 2
Views: 1010
Reputation: 299
As far as I can tell this is a bug in UICollectionView
, and I've opened a Radar myself. You might want to do so as well to apply more pressure.
Even before prefetching, collection view didn't bother deselecting cells that weren't visible. Even cellForItemAtIndexPath:
states that it returns nil
for cells that are not visible.
But before prefetching, as soon as a cell left the content view it was added to the reuse pool, and when you scrolled back you got a reused cell that had its selected state reset.
Now, that cell remains loaded and wont reset until it's reused, by scrolling further away, beyond the prefetching area.
To fix this you can either set prefetchingEnabled
to NO
and lose its benefits, or update selection state whenever a cell appears -
- (void)collectionView:(UICollectionView *)collectionView
willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
if (!cell.isSelected) {
return;
}
if ([[collectionView indexPathsForSelectedItems] containsObject:indexPath]) {
return;
}
cell.selected = NO;
}
This doesn't seem to diminish performance.
Upvotes: 1