cellForItem in Collection returns nil just after reload

I want to redraw the content of cells in CollectionView. And reload the collection.

dataSource for Collection is data


Doing this works:


    @IBAction func change(_ sender: UISwitch) {
  
        for iCell in 0..<data.count
            let indexPath = IndexPath(row: iCell, section: 0)
            if let aCell = myCollection.cellForItem(at: indexPath) as? MyCollectionViewCell {
                aCell.someProperty = newValue
                aCell.setNeedsDisplay()
            }
        }
       myCollection.reloadData()
    }


But, if I reload first, myCollection.cellForItem return nil, so nothing happens.


    @IBAction func change(_ sender: UISwitch) {
  
      myCollection.reloadData()

        for iCell in 0..<data.count
            let indexPath = IndexPath(row: iCell, section: 0)
            if let aCell = myCollection.cellForItem(at: indexPath) as? MyCollectionViewCell {
                aCell.someProperty = newValue
                aCell.setNeedsDisplay()
            }
        }
     }


Why ? Is it because myCollection.cellForItem is called before reload has completed ? Could not find hint in Apple's doc.

Accepted Reply

The reload has completed, but that just means that the old cells were thrown away. New cells aren't created until they're needed for display. In a sense, the reload doesn't "know" which cells need to be redisplayed, so it doesn't recreate any of them.


In general, therefore, you should set properties (e.g. "someProperty") on cells when you create them in your "collectionView(_:,cellForItemAt:)" data source method.


If "someProperty" is information that needs to be available on cells that haven't been created yet (so to speak), then you should likely adjust your design to put "someProperty" in your data model instead. If that isn't possible because "someProperty" is specific to that particular collection view, then you'll need to subclass the view controller and keep a side array of "someProperty" values separate from the cells.

Replies

The reload has completed, but that just means that the old cells were thrown away. New cells aren't created until they're needed for display. In a sense, the reload doesn't "know" which cells need to be redisplayed, so it doesn't recreate any of them.


In general, therefore, you should set properties (e.g. "someProperty") on cells when you create them in your "collectionView(_:,cellForItemAt:)" data source method.


If "someProperty" is information that needs to be available on cells that haven't been created yet (so to speak), then you should likely adjust your design to put "someProperty" in your data model instead. If that isn't possible because "someProperty" is specific to that particular collection view, then you'll need to subclass the view controller and keep a side array of "someProperty" values separate from the cells.

I have to return to the question.


So, now I have the follosing:

    @IBAction func change(_ sender: UISwitch) { 
   
        for iCell in 0..<data.count 
            let indexPath = IndexPath(row: iCell, section: 0) 
            if let aCell = myCollection.cellForItem(at: indexPath) as? MyCollectionViewCell { 
                aCell.setNeedsDisplay() 
            } 
        } 
       myCollection.reloadData() 
    }


But the problem now is that myCollection.cellForItem read the cell before reloading the new data ! Hence, the draw that is called


So, I tried calling

cell.setNeedsDisplay()

just before returning cell at the end of

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {


That seems to fix it. But is it a robust design ?

What's wrong with doing just this:


    @IBAction func change(_ sender: UISwitch) { 
       myCollection.reloadData()  
    } 


Reloading the table view's data tells the table view that none of its cells can be assumed to be valid any more. That implicitly forces all cells to be discarded and recreated (or re-used). That should have the side-effect of causing all visible cells to be redrawn.

Thanks for suggestion. I tried.


But this has other side effects of impeding redraws in other interactions (probably I would need to check the logic of how I do cell updates in my code ?).


So, I would like for the time being to keep with setNeedsDisplay at the end of

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {


But is that safe ? Or could it have some side effect ?

>>But this has other side effects of impeding redraws in other interactions


I don't understand. You're doing the reloadData anyway. What does forcing individual cells to redraw (again) add to the outcome?


But in answer to your specific question, if you do the setNeedRedraws after reloadData, it's safe enough. At worst you're causing some extra redrawing.

As often, the problem was not at all where I thought it was ! And forcing a needsDisplay in collectionView(_ collectionView: cellForItemAt 🙂 was totally useless !


In fact, the update issue came from affineTransfroms (rotations) that I unproperly managed in the draw() of a cell.

I had just forgotten that affineTransform were excuted async, and that the last affine set to a view somehow overrides any earlier transform that was not executed.


After correcting in cell draw, everything seems now OK.


Thanks again for the help.