cellForItemAtIndexPath returns nil after force scrolling to make it visible

11,608

Solution 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;

Solution 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.

Solution 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.

Solution 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>>
    }
Share:
11,608

Related videos on Youtube

Kevin DiTraglia
Author by

Kevin DiTraglia

Updated on September 15, 2022

Comments

  • Kevin DiTraglia
    Kevin DiTraglia over 1 year

    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?

    • Kevin DiTraglia
      Kevin DiTraglia
      I actually just want the location of a cell because I'm animating something else to that location during a transition. So what I need isn't really a part of the datasource, but a part of where it is going to be drawn in the screen.
    • Aaron Brager
      Aaron Brager
      I think there's a problem in your design if you need a reference to your cell. You should be able to update your data model with your changes, scroll to the cell, and what you want should appear. You shouldn't need to then get the cell and modify it.
  • Kevin DiTraglia
    Kevin DiTraglia about 10 years
    Ah very cool. The only problem now is I actually animate to the location of a subview in the cell, which I am unable to obtain with this method. I could kind of fudge it since it's always in the same spot, because I imagine this has some performance benefits from how I am doing it.
  • Kevin DiTraglia
    Kevin DiTraglia almost 10 years
    Revisiting this months later, this is now the solution, so giving you the super late checkmark.
  • RPM
    RPM about 8 years
    Thanks this was very helpful !
  • Shaybc
    Shaybc about 8 years
    I searched long time to force collectionView to display a certain cell as focused and selected and your method (as expensive as it looks) is the sole working solution, thanks for investing the time to suggest it,
  • jaredsinclair
    jaredsinclair almost 8 years
    Verified this works for me, too. I think it's because AutoLayout passes aren't run every time one might expect them to.