Skip to content

Instantly share code, notes, and snippets.

@phlippieb
Created March 25, 2019 12:37
Show Gist options
  • Save phlippieb/af6043872d2ebec477c319148c3c4d37 to your computer and use it in GitHub Desktop.
Save phlippieb/af6043872d2ebec477c319148c3c4d37 to your computer and use it in GitHub Desktop.
//
// TableviewDatasource.swift
//
// Created by Phlippie Bosman on 2019/01/30.
// Copyright © 2019 Kalido. All rights reserved.
//
import Reusable
import SwiftLCS
/// A datasource for a single section.
/// Inspired by a post by John Sundell.
/// Works with Reusable cells.
class TableViewSectionDataSource<CellType: UITableViewCell & Reusable, ModelType: Hashable>: NSObject, UITableViewDataSource {
/// The signature for a closure that configures a given cell, when given a model instance.
typealias CellConfigurator = (CellType, ModelType) -> Void
// State
init(tableView: UITableView, cellConfigurator: @escaping CellConfigurator) {
// Set the local properties.
self.tableView = tableView
self.cellConfigurator = cellConfigurator
super.init()
// Configure the tableView to dequeue the required cell types.
tableView.register(cellType: CellType.self)
tableView.register(cellType: BlankTableCell.self)
tableView.dataSource = self
}
private let tableView: UITableView
private let cellConfigurator: CellConfigurator
// Data
/// The data to display in the tableview.
internal private(set) var models: [ModelType] = []
/// Update the list of models to display.
/// Animated.
/// Dispatched on the main thread.
internal func setModels(to newModels: [ModelType]) {
DispatchQueue.main.async { [weak self] in
self?._setModels(to: newModels)
}
}
private func _setModels(to newModels: [ModelType]) {
// Determine which index paths to delete and insert
let diff = self.models.diff(newModels)
let indexPathsToDelete = self.getIndexPathsForDeletions(from: diff)
let indexPathsToinsert = self.getIndexPathsForInsertions(from: diff)
// Update the underlying data
self.models = newModels
// Apply the updates
self.tableView.beginUpdates()
self.tableView.deleteRows(at: indexPathsToDelete, with: .automatic)
self.tableView.insertRows(at: indexPathsToinsert, with: .automatic)
self.tableView.endUpdates()
}
private func getIndexPathsForDeletions(from diff: Diff<Int>) -> [IndexPath] {
return diff.removedIndexes.map {
IndexPath(row: $0, section: 0)
}
}
private func getIndexPathsForInsertions(from diff: Diff<Int>) -> [IndexPath] {
return diff.addedIndexes.map {
IndexPath(row: $0, section: 0)
}
}
// UITableViewDataSource
// (Note: cannot be an exception because this class is generic)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return self.getConfiguredCell(for: indexPath)
?? self.getBlankCell(for: indexPath)
}
private func getConfiguredCell(for indexPath: IndexPath) -> CellType? {
guard let model = self.models.item(at: indexPath.row)
else { return nil }
let cell: CellType = self.tableView.dequeueReusableCell(for: indexPath, cellType: CellType.self)
self.cellConfigurator(cell, model)
return cell
}
private func getBlankCell(for indexPath: IndexPath) -> BlankTableCell {
let blankCell: BlankTableCell = self.tableView.dequeueReusableCell(for: indexPath)
return blankCell
}
}
/// A composing class that allows us to have use multiple data sources, one per section.
class TableViewDataSource: NSObject {
/// - Parameter dataSources: An ordered list of data sources to use for each section.
init(dataSources: [UITableViewDataSource]) {
self.dataSources = dataSources
}
private let dataSources: [UITableViewDataSource]
}
extension TableViewDataSource: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return self.dataSources.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let dataSourceForSection = self.dataSources.item(at: section)
else { return 0 }
return dataSourceForSection.tableView(tableView, numberOfRowsInSection: section)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let dataSourceForSection = self.dataSources.item(at: indexPath.section)
else { return tableView.dequeueReusableCell(for: indexPath, cellType: BlankTableCell.self) }
return dataSourceForSection.tableView(tableView, cellForRowAt: indexPath)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment