UICollectionView is one of the most important tools in our toolbox as iOS developers. Achieving smooth scrolling performance is a badge of honor for many of us. This year in iOS 10, Apple has made substantial changes to how cells make their way into collection views.
Today we'll take a look at these changes, and how we can take advantage of them in our apps. Let's dive in! 🏊
Here's the TLDR:
UICollectionView will now call
cellForItemAtIndexPath: way before it used to. Sometimes the cells we return from that function won't even end up being displayed. We should proceed accordingly.
This dramatically improves scrolling performance as the system intelligently manages everything, giving us beautiful, buttery-smooth, 60fps scrolling. Neat!
Here's the slightly more wordy version:
In iOS 10, Apple has enhanced UICollectionView's ability to "pre-load" cells before they appear on screen.
Essentially, UICollectionView is now aware of the direction the user is scrolling, and will "look ahead" to cells that will be coming on to screen soon, call the appropriate existing delegate functions (like
cellForItemAtIndexPath:) as needed.
Let's look at how the UICollectionViewCell lifecycle has changed in iOS 10. We'll start with how things are today, in iOS 9:
cellForItemAtIndexPath: is called, and we dequeue a cell. UICollectionView pulls it from the reuse queue, and calls
prepareForReuse on it.
Next, we'll configure the cell to our needs. Setting the content of labels, etc.
Then, right before the cell is scrolled into view,
willDisplayCell:atIndexPath: is called.
The user looks at the beautiful cell we've created until they grow tired of it, and eventually scroll it off screen.
At this point, we'll get a
didEndDisplayingCell:atIndexPath: call, and our cell will re-enter the reuse queue.
If the user changes their mind and scrolls back up, the whole process starts over with
Now, how about in iOS 10? Next we'll look at how this process has improved:
In iOS 10, things are mostly the same, all the way up until the point where the cell goes off screen at the end.
didEndDisplayingCell:atIndexPath: is called as usual, but the cell is not immediately put into the reuse queue.
Instead, the system keeps it around for a bit. This way, if the user starts scrolling the other direction, the cell is already ready to be displayed on screen again. Very cool!
UICollectionView also has a new trick when working with multi-column layouts.
(This is where the “prefetching” bit comes in).
In these layouts, the system will optimize for scrolling performance by sending cells through their lifecycle sooner, at times determined intelligently by the system.
Cells will go through their lifecycle one at a time, each being created via an incoming call to
Later, once the entire "row" of cells is about to be scrolled on to screen,
willDisplayCell:atIndexPath: will be called on each cell.
Apple describes cell pre-fetching as an "adaptive" technology. In other words, it will respond to how users are interacting with our apps.
For example, UICollectionView will try to look for "down time" when the user isn't scrolling (or at least isn't scrolling quickly) to prefetch some cells. If the user begins wildly scrolling very quickly, cell prefetching will stop until they slow down.
Finally, here's some general advice and info that we should consider when architecting our UICollectionView code going forward:
Set up our cells in
cellForRowIndexPath:, try to keep the
didEndDisplaycallbacks light weight.
We should now expect some cells to be created (
cellForItemAtIndexPath:) but never displayed.
If for some wacky reason we didn't want this wonderful new behavior, we can set our collection view's
isPrefetchingEnabled property to
false (it defaults to
true in iOS 10).
These improvements are obviously really great, but what about actually doing the work of loading the data that gets displayed in our cells. Well, for that you'll have to come back for tomorrow's Bite!