cellForItemAtIndexPath returns nil after force scr

2019-01-23 14:15发布

问题:

So I am trying to write some code that scrolls a collection view to a certain index, then pulls a reference to the cell and does some logic. However I've noticed if that cell wasn't presently visible prior to the scroll, the cellForItemAtIndexPath call will return nil, causing the rest of my logic to fail.

[_myView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:index 
                                                     inSection:0] 
                atScrollPosition:UICollectionViewScrollPositionTop
                        animated:NO];

//Tried with and without this line, thinking maybe this would trigger a redraw
[_myView reloadData];

//returns nil if cell was off-screen before scroll
UICollectionViewCell *cell = 
  [_myView cellForItemAtIndexPath:
    [NSIndexPath indexPathForItem:index inSection:0]];

Is there some other method I have to call to cause the cellForItemAtIndexPath to return something for a cell that suddenly came into view as a result of the scroll immediately preceding it?

回答1:

Based on your comments, it sounds like what you're ultimately after is the cell's frame. The way to do that without relying on existence of the cell is to ask the collection view's layout:

NSIndexPath *indexPath = ...;
UICollectionViewLayoutAttributes *pose = [self.collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
CGRect frame = pose.frame;


回答2:

I figured out a solution for now. If I call [myView layoutIfNeeded] right after my call to reloadData, but before my attempt to retrieve the cell everything works fine. Right now all the cells are cached so access is fast, but I am scared this might give me bad performance if I have to load from the web or an internal database, but we'll see.



回答3:

If you want to access the cell and have it visible on screen:

NSIndexPath *indexPath = ...;

[collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically | UICollectionViewScrollPositionCenteredHorizontally animated:NO];

UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];

if(cell == nil) {
    [collectionView layoutIfNeeded];
    cell = [collectionView cellForItemAtIndexPath:indexPath];
}

if(cell == nil) {
    [collectionView reloadData];
    [collectionView layoutIfNeeded];
    cell = [collectionView cellForItemAtIndexPath:indexPath];
}

I very rarely enter the second if statement, but having two fallbacks like this works very well.



回答4:

hfossli's solution worked for me, so I've provided a Swift version below. In my case, I was unable to get a reference to a custom UICollectionViewCell, probably because it was not visible. I tried many things, but just as Shaybc said, this was the sole working solution.

Swift 3:

  func getCell(_ indexPath: IndexPath) -> CustCell? {
    cView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition.centeredHorizontally, animated: false)
    var cell = cView.cellForItem(at: indexPath) as? CustCell
    if cell == nil {
      cView.layoutIfNeeded()
      cell = cView.cellForItem(at: indexPath) as? CustCell
    }
    if cell == nil {
      cView.reloadData()
      cView.layoutIfNeeded()
      cell = cView.cellForItem(at: indexPath) as? CustCell
    }
    return cell
  }

usage:

let indexPath = IndexPath(item: index, section: 0)
cView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition(), animated: true)
if let cell = getCell(indexPath) {
      cell. <<do what you need here>>
    }