Skip to content

Instantly share code, notes, and snippets.

@smosko
Created August 21, 2022 12:07
Show Gist options
  • Save smosko/8e6667fa997242c56a892b997cca981e to your computer and use it in GitHub Desktop.
Save smosko/8e6667fa997242c56a892b997cca981e to your computer and use it in GitHub Desktop.
Generic cell provider for UICollectionViewDiffableDataSource
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