UICollectionView deselectItem when cell pre-fetching is enabled

有些话、适合烂在心里 提交于 2019-12-24 02:17:31

问题


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:

  • Tap on a cell, so that it becomes selected (red)
  • Scroll the cell slightly off-screen
  • Tap the "Deselect Cell" button
  • Scroll the cell back on screen
  • Observe how it still looks selected
  • Tap on another cell
  • Observe how both cells look selected
  • Scroll the first cell far off-screen and back again
  • Observe how the first cell finally does not look selected


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

回答1:


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.



来源:https://stackoverflow.com/questions/40193279/uicollectionview-deselectitem-when-cell-pre-fetching-is-enabled

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!