Created
August 21, 2022 12:07
-
-
Save smosko/8e6667fa997242c56a892b997cca981e to your computer and use it in GitHub Desktop.
Generic cell provider for UICollectionViewDiffableDataSource
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
protocol ConfigurableCell: UICollectionViewCell { | |
associatedtype Item | |
func configure(with item: Item, for indexPath: IndexPath) | |
} | |
extension ConfigurableCell { | |
public static var provider: (UICollectionView, IndexPath, Item) -> Self { | |
let registration = registration() | |
return { collectionView, indexPath, item in | |
collectionView.dequeueConfiguredReusableCell(using: registration, for: indexPath, item: item) | |
} | |
} | |
public static func provider<ID>(_ lookup: @escaping (ID) -> Item?) -> (UICollectionView, IndexPath, ID) -> Self? { | |
let registration = registration() | |
return { collectionView, indexPath, itemId in | |
guard let item = lookup(itemId) else { return nil } | |
return collectionView.dequeueConfiguredReusableCell(using: registration, for: indexPath, item: item) | |
} | |
} | |
private static func registration() -> UICollectionView.CellRegistration<Self, Item> { | |
.init { cell, indexPath, item in | |
cell.configure(with: item, for: indexPath) | |
} | |
} | |
} | |
class ViewController: UIViewController { | |
private enum Section { case main } | |
private typealias DataSource = UICollectionViewDiffableDataSource<Section, Product.ID> | |
private typealias Snapshot = NSDiffableDataSourceSnapshot<Section, Product.ID> | |
private lazy var collectionView: UICollectionView = { | |
let configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) | |
let layout = UICollectionViewCompositionalLayout.list(using: configuration) | |
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) | |
collectionView.refreshControl = refreshControl | |
return collectionView | |
}() | |
private lazy var dataSource = DataSource( | |
collectionView: collectionView, | |
cellProvider: ProductCell.provider { Product.byID($0) } | |
) | |
private lazy var refreshControl = UIRefreshControl( | |
frame: .zero, | |
primaryAction: UIAction { [unowned self] _ in | |
refreshData() | |
} | |
) | |
override func loadView() { | |
view = collectionView | |
} | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
applyInitialSnapshot() | |
} | |
private func applyInitialSnapshot() { | |
var snapshot = Snapshot() | |
snapshot.appendSections([.main]) | |
snapshot.appendItems(Product.allProductIDs) | |
dataSource.applySnapshotUsingReloadData(snapshot) | |
} | |
private func refreshData() { | |
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { | |
Product.updatePrices() | |
self.reconfigureItems() | |
self.refreshControl.endRefreshing() | |
} | |
} | |
private func reconfigureItems() { | |
var snapshot = dataSource.snapshot() | |
snapshot.reconfigureItems(Product.allProductIDs) // preserves the existing cells | |
dataSource.apply(snapshot, animatingDifferences: false) | |
} | |
} | |
class ProductCell: UICollectionViewListCell, ConfigurableCell { | |
public func configure(with product: Product, for indexPath: IndexPath) { | |
var configuration = defaultContentConfiguration() | |
configuration.text = product.name | |
configuration.secondaryText = String(product.price) | |
configuration.secondaryTextProperties.color = .secondaryLabel | |
configuration.prefersSideBySideTextAndSecondaryText = true | |
contentConfiguration = configuration | |
accessories = [.disclosureIndicator()] | |
} | |
} | |
struct Product: Identifiable { | |
let id: UUID | |
let name: String | |
var price: Double | |
static var allProducts = [ | |
Product(id: UUID(), name: "Americano", price: 2.99), | |
Product(id: UUID(), name: "Cappuccino", price: 3.99), | |
Product(id: UUID(), name: "Latte", price: 4.99), | |
] | |
static var allProductIDs: [UUID] { | |
allProducts.map(\.id) | |
} | |
static func byID(_ id: UUID) -> Product? { | |
allProducts.first(where: { $0.id == id }) | |
} | |
static func updatePrices() { | |
for index in allProducts.indices { | |
allProducts[index].price += 1 | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment