Doto Pototo
Doto Pototo

Reputation: 705

Load images for collection view in background thread

So I have a collection view in which I'm populating 141 images into it, but the problem I'm having is when scrolling through the view, it's not very smooth. I've already tried using low res images for the cells (which helped slightly) and decreasing the swipe speed (or should I say increasing the deceleration when scrolling).

So I figured my next step would be to try loading the cells on a background thread?

Firstly I have no clue how to do this, and Google isn't much help. Secondly, I'm not sure if the fact I'm re-using cells when they go out of view (so I don't create 141 cells, one cell per image) makes a difference.

Here's how I'm loading in the images:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

    if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("TreeCell", forIndexPath: indexPath) as? TreeCell {
        cell.contentView.backgroundColor = UIColor.clearColor()
        let tree = Tree(treeNumber: (indexPath.row + 1))
        cell.configureCell(tree)
        return cell
    }

    else {
        return UICollectionViewCell()
    }
} 

TreeCell for whoever asked:

var tree: Tree!

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)

    layer.cornerRadius = 10.0
}

func configureCell(tree: Tree) {
    self.tree = tree

    treeCellImage.image = UIImage(named: "\(self.tree.treeNumber)")
}

And the Tree class:

private var _treeNumber: Int!

init(treeNumber: Int) {
    _treeNumber = treeNumber
}

var treeNumber: Int {
    return _treeNumber
}

Upvotes: 0

Views: 2934

Answers (2)

Kelvin Lau
Kelvin Lau

Reputation: 6781

Based on your previous comment on the image size, the first thing you should do is to resize your 200 x 200 images to match the 90 x 90 dimensions of your collectionViewCell.

UIKit will resize the 200 x 200 images as you populate your cells. Resizing images in general incur a non trivial cost, and this is done on the main thread. You should see drastic increase in smooth scrolling if you gave your cells 90 x 90 images instead of 200 x 200.

Upvotes: 0

Adam Kaplan
Adam Kaplan

Reputation: 1962

Don't try to create UICollectionViewCells on a background thread. The cells should be created only via a dequeueReusableCellWithReuseIdentifier variant and UI prefixed classes (UIKit) are not thread-safe unless explicitly stated as such. As you said, it will break cell reuse and potentially cause all sorts of memory issues.

Check out this SO question: Load UIImage in background Thread

If you aren't supporting iOS 8, you could load the images from disk on the background. Once they're loaded, I'd post a NSNotification on the main thread containing the image's unique id (filename?). The cells can all listen to the notification and – if the notification is observed for the cell's current image – display the new UIImage.

If you can use [UIImage imageNamed:], you'll get some nice low-memory-reactive caching benefits for free. Otherwise, you'll probably want to maintain a cache (NSCache or NSDictionary) of the images that you're already loaded.

If you do need to support iOS 8, you can still load image data from disk in the background via NSData, and feed the image data into a UIImage on the main thread. The expensive task is reading from disk. If your images are in Asset Catalogs, this will require a lot more work than imageNamed.

Now, assuming you find a method of safely loading the images on a background thread/queue that works in your specific situation, you have a few options to further optimize performance:

Cap concurrent tasks

Try to limit the number of concurrent image loads otherwise a quick scroll can saturate the system with blocking tasks and thread switching overhead.

Cancellable tasks

You should be able to cancel pending image load operations. If the user is scrolling just a bit faster than you can load the images, you want to be able to cancel loads for images that have already moved off-screen. Otherwise you can fill up the queue with unseen images.

Intelligent image loading

Don't load images that will be scrolled away in milliseconds (i.e. in response to a 'flick' or quick swipe). You can figure this out by hooking into the collection view UIScrollViewDelegate method -scrollViewWillEndDragging:withVelocity:targetContentOffset:. This method lets you know how fast a user dragged the content and where the collection view will end up. You can even pre-load the images near the target content offset so that they're ready when scrolling ends!

A good for this sort of thing is NSOperationQueue because NSOperation instances support prioritization, concurrency caps, and concurrent execution. If you go with Grand Central Dispatch, you'll need to build in the ability to 'cancel' a task on your own.

Upvotes: 4

Related Questions