Created
March 25, 2019 12:37
-
-
Save phlippieb/af6043872d2ebec477c319148c3c4d37 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// | |
// 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