-
Make blazing fast lists and collection views
Build consistently smooth scrolling list and collection views: Explore the lifecycle of a cell and learn how to apply that knowledge to eliminate rough scrolling and missed frames. We'll also show you how to improve your overall scrolling experience and avoid costly hitches, with optimized image loading and automatic cell prefetching.
To get the most out of this video, we recommend a basic familiarity with diffable data sources and compositional layout.Recursos
Videos relacionados
WWDC21
WWDC20
Tech Talks
-
Buscar este video…
-
-
1:25 - Structuring data
// Structuring data struct DestinationPost: Identifiable { // Each post has a unique identifier var id: String var title: String var numberOfLikes: Int var assetID: Asset.ID } -
2:01 - Setting up diffable data source
// Setting up diffable data source class DestinationGridViewController: UIViewController { // Use DestinationPost.ID as the item identifier var dataSource: UICollectionViewDiffableDataSource<Section, DestinationPost.ID> private func setInitialData() { var snapshot = NSDiffableDataSourceSnapshot<Section, DestinationPost.ID>() // Only one section in this collection view, identified by Section.main snapshot.appendSections([.main]) // Get identifiers of all destination posts in our model and add to initial snapshot let itemIdentifiers = postStore.allPosts.map { $0.id } snapshot.appendItems(itemIdentifiers) dataSource.apply(snapshot, animatingDifferences: false) } } -
3:47 - Creating cell registrations
// Cell registrations let cellRegistration = UICollectionView.CellRegistration<DestinationPostCell, DestinationPost.ID> { (cell, indexPath, postID) in let post = self.postsStore.fetchByID(postID) let asset = self.assetsStore.fetchByID(post.assetID) cell.titleView.text = post.region cell.imageView.image = asset.image } -
4:03 - Using cell registrations
// Cell registrations let cellRegistration = UICollectionView.CellRegistration<DestinationPostCell, DestinationPost.ID> { (cell, indexPath, postID) in ... } let dataSource = UICollectionViewDiffableDataSource<Section.ID, DestinationPost.ID>(collectionView: cv){ (collectionView, indexPath, postID) in return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: postID) } -
13:58 - Existing cell registration
// Existing cell registration let cellRegistration = UICollectionView.CellRegistration<DestinationPostCell, DestinationPost.ID> { (cell, indexPath, postID) in let post = self.postsStore.fetchByID(postID) let asset = self.assetsStore.fetchByID(post.assetID) cell.titleView.text = post.region cell.imageView.image = asset.image } -
14:17 - Updating cells asynchronously (wrong)
// Updating cells asynchronously let cellRegistration = UICollectionView.CellRegistration<DestinationPostCell, DestinationPost.ID> { (cell, indexPath, postID) in let post = self.postsStore.fetchByID(postID) let asset = self.assetsStore.fetchByID(post.assetID) if asset.isPlaceholder { self.assetsStore.downloadAsset(post.assetID) { asset in cell.imageView.image = asset.image } } cell.titleView.text = post.region cell.imageView.image = asset.image } -
15:15 - Reconfiguring items
private func setPostNeedsUpdate(id: DestinationPost.ID) { var snapshot = dataSource.snapshot() snapshot.reconfigureItems([id]) dataSource.apply(snapshot, animatingDifferences: true) } -
15:23 - Updating cells asynchronously (correct)
// Updating cells asynchronously let cellRegistration = UICollectionView.CellRegistration<DestinationPostCell, DestinationPost.ID> { (cell, indexPath, postID) in let post = self.postsStore.fetchByID(postID) let asset = self.assetsStore.fetchByID(post.assetID) if asset.isPlaceholder { self.assetsStore.downloadAsset(post.assetID) { _ in self.setPostNeedsUpdate(id: post.id) } } cell.titleView.text = post.region cell.imageView.image = asset.image } -
15:52 - Data source prefetching
// Data source prefetching var prefetchingIndexPaths: [IndexPath: Cancellable] func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths [IndexPath]) { // Begin download work for indexPath in indexPaths { guard let post = fetchPost(at: indexPath) else { continue } prefetchingIndexPaths[indexPath] = assetsStore.loadAssetByID(post.assetID) } } func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) { // Stop fetching for indexPath in indexPaths { prefetchingIndexPaths[indexPath]?.cancel() } } -
18:43 - Using prepareForDisplay
// Using prepareForDisplay // Initialize the full image let fullImage = UIImage() // Set a placeholder before preparation imageView.image = placeholderImage // Prepare the full image fullImage.prepareForDisplay { preparedImage in DispatchQueue.main.async { self.imageView.image = preparedImage } } -
19:51 - Asset downloading without image preparation
// Asset downloading – before image preparation func downloadAsset(_ id: Asset.ID, completionHandler: @escaping (Asset) -> Void) -> Cancellable { return fetchAssetFromServer(assetID: id) { asset in DispatchQueue.main.async { completionHandler(asset) } } } -
19:58 - Asset downloading with image preparation
// Asset downloading – with image preparation func downloadAsset(_ id: Asset.ID, completionHandler: @escaping (Asset) -> Void) -> Cancellable { // Check for an already prepared image if let preparedAsset = imageCache.fetchByID(id) { completionHandler(preparedAsset) return AnyCancellable {} } return fetchAssetFromServer(assetID: id) { asset in asset.image.prepareForDisplay { preparedImage in // Store the image in the cache. self.imageCache.add(asset: asset.withImage(preparedImage!)) DispatchQueue.main.async { completionHandler(asset) } } } } -
20:50 - Using prepareThumbnail
// Using prepareThumbnail // Initialize the full image let profileImage = UIImage(...) // Set a placeholder before preparation posterAvatarView.image = placeholderImage // Prepare the image profileImage.prepareThumbnail(of: posterAvatarView.bounds.size) { thumbnailImage in DispatchQueue.main.async { self.posterAvatarView.image = thumbnailImage } }
-