UIcollectionView cellForItemAtIndexPath returns Null in iOS 10 only. Works fine in iOS 9 and iOS 8

本秂侑毒 提交于 2021-02-07 09:19:03

问题


I have an app that been happily shipping for a couple of years. It retrieves RSS Feeds in a UICollectionView. The cellForItemAtIndexPath method sets text and calls a datasource method to load an image from a link specified in the feed. If none exists it loads the web page data and searches for <img> tags to get an image. Once the image is found/loaded the delegate method is called to add the image to the cell. (below)

When running in iOS 8 and 9 everything is happy, but when running in iOS 10 the visible cells are updated with images when the RSS feed is initially loaded but when scrolling no images are added and I get NULL from cellForItemAtIndexPath.

The image is displayed when I scroll back and the image is displayed if I add a reloadItemsAtIndexPaths to imageWasLoadedForStory but reloadItemsAtIndexPaths destroys performance.

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
           // ...    

          if (story.thumbnail) {
          [imageView setAlpha: 1.0];
          imageView.image = story.thumbnail;
          UIActivityIndicatorView *lActivity = (UIActivityIndicatorView *) [collectionView viewWithTag: 900];
          [lActivity removeFromSuperview];
          }else{
               [story setDelegate: self];
               [story findArticleImageForIndexPath: indexPath];
          }
          //  ...
}



//delegate method.  Called when image has been loaded for cell at specified indexpath

- (void) imageWasLoadedForStory: (RSSStory *) story forIndexPath: (NSIndexPath *) indexPath
{
        //get cell
        CustomCollectionViewCell *customCell = (id) [self.collectionview cellForItemAtIndexPath: indexPath];

        NSLog(@"imageWasLoadedForStory row %i section %i  and class %@", (int)indexPath.row, (int)indexPath.section, [customCell class]);

        //if cell is visible ie: cell is not nil then update imageview
        if (customCell) {
                 UIImageView *imageView = (UIImageView *) [customCell viewWithTag: 300];
                 imageView.image = story.thumbnail;
                 UIActivityIndicatorView *lActivity = (UIActivityIndicatorView *) [customCell viewWithTag: 900];
                 [lActivity removeFromSuperview];
                 [customCell setNeedsLayout];
                 [customCell setNeedsDisplay];
                 }
                    //[self.collectionview reloadItemsAtIndexPaths: [NSArray arrayWithObject: indexPath]];           
}



- (void) findArticleImageForIndexPath: (NSIndexPath *) indexPath
{
           //kick off image search
           dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

                self.thumbnail = [self findArticleForStoryForIndexPath:indexPath];
                dispatch_async( dispatch_get_main_queue(), ^{
                //image set - return
                      [self.delegate imageWasLoadedForStory: self forIndexPath: indexPath];
                });
        });
}

回答1:


I encourage everyone to read up on Prefetching - new in iOS 10. The simple solution is this:

[self.customCollectionview setPrefetchingEnabled:NO];

As it turns out cells are now prefetched. This means there is now a difference between loaded/nonvisible cells and loaded/visible cells.

In iOS 10 a cell can now be preloaded but it will still return nil if it's not visible. So cellForItemAtIndexPath is called to preload a cell. It is then entirely possible the image will finish loading and cellForItemAtIndexPath will return nil if the cell is not visible. That means the imageView will not be set. When scrolling the image will not be added to the cell since the cell was already created.

Getting loaded vs visible cells on a UITableView or UICollectionView




回答2:


Make sure cell you're trying to access with [self.collectionview cellForItemAtIndexPath: indexPath] is visible.

It will always return nil if it is not visible.

You should access your datasource (array, core data, etc.) to get data you're showing in that cell instead of accessing cell itself.




回答3:


As per Joe Fratianni's answer this is due to prefetching, which can be disabled. Of course, that also loses the benefits of prefetching.

The approach recommended by Apple in the documentation for collectionView(_:cellForItemAt:) is to instead update cell appearance in collectionView(_:willDisplay:forItemAt:).

That way, the cell is either visible and available from cellForItem(at:) for ad-hoc updates, or is not visible but gets up-to-date information when it scrolls into view.


Another alternative that worked for me with a simpler code change while retaining some benefit of prefetching was to wrap the updates with calls to cellForItem(at indexPath) within a performBatchUpdates block, e.g.:

class MyViewController: UIViewController {
    @IBOutlet weak var collectionView: UICollectionView!
    ...
    func someMethod() {
        ...
        collectionView.performBatchUpdates {
            ...
            if let cell = collectionView.cellForItem(at: indexPath) as? MyCell {
                // Update cell, e.g.:
                cell.someField = someValue
            }
            ...
        } completion: { _ in }
        ...
    }
    ...
}

Apparently the performBatchUpdates call discards any cells that have been prepared in collectionView(_:cellForItemAt:) but are not yet visible (due to prefetch).

Unfortunately, it seems those discarded cells aren’t prefetched again, and will be reloaded only once they become visible. So some benefit of prefetching is lost.



来源:https://stackoverflow.com/questions/40322995/uicollectionview-cellforitematindexpath-returns-null-in-ios-10-only-works-fine

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