8
votes

I have a UICollectionView which shows images retrieved from the web. They are downloaded asynchronous.

When user scrolls fast, they see placeholders until the cell loads. It seems UICollectionView only loads what is visible.

Is there a way to say "collection view, load 20 cells more above and below" so chance is higher that it loaded more cells while user was looking at content without scrolling?

3
One way i can think of is to make the UICollectionView bigger than its actually visual part (hidden under some mask or so). But this is definetly not the optimal solution :) - Rok Jarc
But since your data is loaded asynchronously: maybe it's not the collectionview but the dataSource problem... - Rok Jarc
how may images you have in your collection, because if you load all the images at the same time you can going to have a memory warning - Mirko Catalano
This is not a problem that the UICollectionView should be responsible to solve. It's loading cells instantly, but you want your model to be more ready when it does. Write VC code that asks your model for the next or last N urls and start fetching those, caching the results. - danh
@rokjarc No, the UICollectionView only requests 10 cells from the data source but it then continues to request more when the user begins scrolling. It should just preload more cells while user is not scrolling. - openfrog

3 Answers

2
votes

The idea is to have the VC recognize when a remote load might be required and start it. The only tricky part is keeping the right state so you don't trigger too much.

Let's say your collection is vertical, the condition you want to know about is when:

BOOL topLoad = scrollView.contentOffset.y < M * scrollView.bounds.size.height

or when

BOOL bottomLoad = scrollView.contentOffset.y > scrollView.contentSize.height - M * scrollView.bounds.size.height

in other words, when we are M "pages" from the edge of the content. In practice though, this condition will be over-triggered, like when you're first loading, or if you're testing it on scrollViewDidScroll, you don't want to generate web requests for every pixel of user scrolling.

Getting it right, therefore, requires additional state in the view controller. The vc can have a pair of BOOLs, like topLoadEnabled, bottomLoadEnabled, that are NO until the view is ready. Then, scroll delegate code looks like this:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {

    // compute topLoad and bottomLoad conditions
    if (topLoad && self.topLoadEnabled) [self startTopLoad];

similarly for bottom. The load code looks like this, abstractly:

self.topLoadEnabled = NO;  // don't trigger more loading until we're done
[self.model getMoreTopStuff:^(NSArray *newStuff, NSError *error) {

    // do view inserts, e.g. tableView.beginUpdates
    self.topLoadEnabled = YES;
}];

Same idea for bottom. We expect the model to fetch for itself (maybe the model has image urls) and cache the result (then the model has images). As the datasource for the view, the view controller gets called upon to configure view cells. I can just naively ask the model for images. The model should answer either fetched images or placeholders.

Hope that makes sense.

2
votes

In my opinion you are making the wrong assumption: cells are just views so you shouldn't treat them as model objects. UICollectionView and UITableView are very efficient because they constantly recycle cells so you should think in therms of pre loading content in the business side of things. Create interactor or viewmodel objects and populate your data source with those, then you'll be able to ask those objects to preload images, if you still wish to do so.

0
votes

A BOOL flag seldom is the answer. I'd rather go for estimating a reasonable page size and fetching images as needed from the cellForItemAtIndePath method.