
git-subtree-dir: submodules/AsyncDisplayKit git-subtree-mainline: d06f423e0ed3df1fed9bd10d79ee312a9179b632 git-subtree-split: 02bedc12816e251ad71777f9d2578329b6d2bef6
8.5 KiB
Executable File
title | layout | permalink |
---|---|---|
UICollectionView Challenges | docs | /docs/uicollectionview-challenges.html |
UICollectionView
is one of the most commonly used classes and many challenges with iOS development are related to its architecture.
How UICollectionView
Works
There are two important methods that UICollectionView
requires.
Cell Measurement
For each item in the data source, the collection must know its size to understand which items should be visible at a given momement. This is provided by:
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
optional func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize
Although not formally named by Apple, we refer to this process as "measuring". Implementing this method is always difficult, because the view that implements the cell layout is never available at the time of this method call.
This means that logic must be duplicated between the implementation of this method and the -layoutSubviews
implementation of the cell subclass. This presents a tremendous maintainence burden, as the implementations must always match their behavior for any combination of content displayed.
Additionally, once measurement is complete, there's no easy way to cache that information to use it during the layout process. As a result, expensive text measurements must be repeated.
Cell Allocation
Once an item reaches the screen, a view representing it is requested:
- (UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath;
func cellForItem(at indexPath: IndexPath) -> UICollectionViewCell?
In order to provide a cell, all subviews must be configured with the data that they are intended to display. Immediately afterwards, the layout of the cell is calculated, and finally the display (rendering) of the individual elements (text, images) contained within.
UICollectionView
communicating with its data source and delegate to display itself.
Limitations in `UICollectionView`'s Architecture
There are several issues with the architecture outlined above:
Lots of main thread work, which may degrade the user's experience, including
- cell measurement
- cell creation + setup / reuse
- layout
- display (rendering)
Duplicated layout logic
You must have duplicate copies of your cell sizing logic for the cell measurement and cell layout stages. For example, if you want to add a price tag to your cell, both -sizeForItemAtIndexPath
and the cell's own -layoutSubviews
must be aware of how to size the tag.
No automatic content loading
There is no easy, universal way to handle loading content such as:
- data pages - such as JSON fetching
- other info - such as images or secondary JSON requests
How ASCollectionNode
works
Unified Cell Measurement & Allocation
Texture takes both of the important collection methods explained above:
- (UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath;
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;
optional func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize
func cellForItem(at indexPath: IndexPath) -> UICollectionViewCell?
and replaces them with a single method*:
- (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForItemAtIndexPath:(NSIndexPath *)indexPath;
func collectionNode(collectionNode: ASCollectionNode, nodeForItemAtIndexPath indexPath: NSIndexPath) -> ASCellNode
or with the asynchronous versions
- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath;
func collectionNode(collectionNode: ASCollectionNode, nodeBlockForItemAtIndexPath indexPath: NSIndexPath) -> ASCellNodeBlock
*Note that there is an optional method to provide a constrained size for the cell, but it is not needed by most apps.
ASCellNode
, is Texture's universal cell class. They are light-weight enough to be created an an earlier time in the program (concurrently in the background) and they understand how to calculate their own size. ASCellNode
automatically caches its measurement so that it can be quickly applied during the layout pass.
ASCollectionView
communicating with its data source and delegate to display itself.. Note that ASCollectionView
is ASCollectionNode
's underlying UICollectionView
subclass.
Benefits of Texture's Architecture
Elimination of all of the types of main thread work described above (cell allocation, measurement, layout, display)! In addition, all of this work is preformed concurrently on multiple threads.
Because ASCollectionNode
is aware of the position of all of its nodes, it can automatically determine when content loading is needed. The Batch Fetching API handles loading of data pages (like JSON) and Intelligent Preloading automatically manages the loading of images and text. Additionally, convenient callbacks allow implementing accurate visibility logging and secondary data model requests.
Lastly, almost all of the concepts we've discussed here apply to UITableView
/ ASTableNode
and UIPageViewController
/ ASPagerNode
.
iOS 10 Cell Pre-fetching
Inspired by Texture, iOS 10 introduced a cell pre-fetching. This API increases the number of cells that the collection tracks at any given time, which helps, but isn't anywhere as performance centric as being aware of all cells in the data source.
Additionally, iOS9 still constitutes a substantial precentage of most app's userbase and will not reduce in number anywhere close to as quickly as the sunset trajectory of iOS 7 and iOS 8 devices. Whereas iOS 9 is the last supported version for about a half-dozen devices, there were zero devices that were deprecated on iOS 8 and only one deivce deprecated on iOS 7.
Unfortunately, these iOS 9 devices are the ones in which performance is most key!