Skip to content

Instantly share code, notes, and snippets.

@vinhnx
Last active October 29, 2020 10:13
Show Gist options
  • Save vinhnx/22c16be8d6a74efb625231540b26ee36 to your computer and use it in GitHub Desktop.
Save vinhnx/22c16be8d6a74efb625231540b26ee36 to your computer and use it in GitHub Desktop.
Diffable datasource and compositional layout
Diffable datasource and compositional layout:
+ https://www.donnywals.com/modern-table-views-with-diffable-data-sources/
+ https://www.swiftbysundell.com/articles/building-modern-collection-views-in-swift/
> **UICollectionViewDiffableDataSource** let us compute UICollectionView datasource and configuration, as a replacement for plain old UICollectionViewDatasource/UICollectionViewDelegate. We also have table view counter part, they are **UITableViewDiffableDataSource**.
> we can now use **UICollectionViewComposableLayout** as replacement for **UITableView**.
```swift
func makeCollectionView() -> UICollectionView {
let layout = makeCollectionViewCompositionalLayout()
return UICollectionView(frame: .zero, collectionViewLayout: layout)
}
func makeGridLayoutSection() -> NSCollectionLayoutSection {
// Each item will take up half of the width of the group
// that contains it, as well as the entire available height:
let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.5),
heightDimension: .fractionalHeight(1)
))
// Each group will then take up the entire available
// width, and set its height to half of that width, to
// make each item square-shaped:
let group = NSCollectionLayoutGroup.horizontal(
layoutSize: NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .fractionalWidth(0.5)
),
subitem: item,
count: 2
)
return NSCollectionLayoutSection(group: group)
}
func makeListLayoutSection() -> NSCollectionLayoutSection {
let item = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(50)))
let inset: CGFloat = 10
item.contentInsets = NSDirectionalEdgeInsets(top: inset, leading: 0, bottom: 0, trailing: 0)
let size = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.vertical(layoutSize: size, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.contentInsets = NSDirectionalEdgeInsets(top: inset, leading: 0, bottom: 0, trailing: 0)
section.interGroupSpacing = 10
// Supplementary header view setup
let headerFooterSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(20)
)
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerFooterSize,
elementKind: UICollectionView.elementKindSectionHeader,
alignment: .top
)
section.boundarySupplementaryItems = [sectionHeader]
return section
}
func makeCollectionViewCompositionalLayout() -> UICollectionViewCompositionalLayout {
return UICollectionViewCompositionalLayout { (sectionIndex, _) in
// we can even switch section index to choose custom layout section
switch sectionIndex {
case 0:
return makeGridLayoutSection()
default:
return makeListLayoutSection()
}
}
}
```
usage example:
```swift
internal typealias DataSource = UICollectionViewDiffableDataSource<Section, Event>
final class EventListViewController: BaseViewController {
// MARK: - Properties
private var collectionView: UICollectionView = makeCollectionView()
private lazy var datasource = makeDatasource()
// MARK: - Override
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
fetchEvents()
}
override func setupViews() {
super.setupViews()
view.backgroundColor = .backgroundColor
}
// MARK: - Public
func fetchEvents(for date: Date = Date()) {
EventHandler.shared.fetchEvents(for: date) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let value):
let items = value.map { Event(event: $0) }
self.applySnapshot(items, date: date)
case .failure(let error):
logError(error)
}
}
}
// MARK: - Datasource
func makeDatasource() -> DataSource {
let datasource = DataSource(
collectionView: collectionView,
cellProvider: { (collectionView, indexPath, event) in
guard let event = event.event else { return nil }
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: EventListTableViewCell.reuseID, for: indexPath) as? EventListTableViewCell
let viewModel = EventListTableViewCell.ViewModel(event: event)
cell?.viewModel = viewModel
return cell
}
)
datasource.supplementaryViewProvider = { collectionView, kind, indexPath in
guard kind == UICollectionView.elementKindSectionHeader else { return nil }
let section = self.datasource.snapshot().sectionIdentifiers[indexPath.section]
let view = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: SectionHeaderReusableView.reuseID,
for: indexPath) as? SectionHeaderReusableView
view?.titleLabel.text = section.date?.toFullDateString
return view
}
return datasource
}
func applySnapshot(_ items: [Event], date: Date?) {
guard let date = date else { return }
var snapshot = NSDiffableDataSourceSnapshot<Section, Event>()
let section = Section(date: date, events: items)
snapshot.appendSections([section])
snapshot.appendItems(items, toSection: section)
datasource.apply(snapshot, animatingDifferences: true, completion: nil)
}
private func setupLayout() {
collectionView = makeCollectionView()
collectionView.backgroundColor = .backgroundColor
// section header
collectionView.register(
SectionHeaderReusableView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: SectionHeaderReusableView.reuseID
)
// cell
collectionView.register(
UINib(nibName: EventListTableViewCell.reuseID, bundle: nil),
forCellWithReuseIdentifier: EventListTableViewCell.reuseID
)
collectionView.dataSource = datasource
collectionView.delegate = self
view.addSubViewAndFit(collectionView)
}
}
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment