Skip to content

Instantly share code, notes, and snippets.

@descorp
Created November 22, 2019 13:39
Show Gist options
  • Save descorp/8017d3285241de672de492d1b37d2ee9 to your computer and use it in GitHub Desktop.
Save descorp/8017d3285241de672de492d1b37d2ee9 to your computer and use it in GitHub Desktop.
Swift Universal Table handlers: allows to load and manage table collections for single type and multiple types of cells
import UIKit
class BaseTableHandler<T> : NSObject, UITableViewDelegate, UITableViewDataSource {
typealias TableSection = (name: String?, items: [T])
typealias Action = (T)->Void
var collection: [TableSection] = []
weak var table: UITableView?
private let selectAction: Action?
private let deleteAction: Action?
init(collection: [T], delete: Action? = nil, select: Action? = nil) {
self.collection = [(name: nil, items: collection)]
self.selectAction = select
self.deleteAction = delete
super.init()
}
init(collection: [TableSection], delete: Action? = nil, select: Action? = nil) {
self.collection = collection
self.selectAction = select
self.deleteAction = delete
super.init()
}
public func register(table: UITableView) {
self.table = table
table.dataSource = self
table.delegate = self
fillEmptySpaceWithFooter = true
}
var fillEmptySpaceWithFooter: Bool {
get {
return table?.tableFooterView != nil
}
set {
if newValue {
table?.tableFooterView = UIView()
} else {
table?.tableFooterView = nil
}
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return collection.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return collection[section].items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
preconditionFailure("This is abstract method")
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return collection[section].name
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
self.selectAction?(self.collection[indexPath.section].items[indexPath.item])
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return deleteAction == nil ? .none : .delete
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return deleteAction != nil
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
deleteAction?(self.collection[indexPath.section].items[indexPath.item])
}
}
}
extension BaseTableHandler where T: Equatable {
func remove(item: T) {
guard
let theSection = self.collection.firstIndex(where: { $0.items.contains(item) }),
let index = self.collection[theSection].items.firstIndex(of: item)
else { return }
self.collection[theSection].items.remove(at: index)
table?.beginUpdates()
table?.reloadSections([theSection], with: .fade)
table?.endUpdates()
}
func updateCollectionWith(item: T) {
guard
let theSection = self.collection.firstIndex(where: { $0.items.contains(item) }),
let index = self.collection[theSection].items.firstIndex(of: item)
else { return }
self.collection[theSection].items[index] = item
table?.beginUpdates()
table?.reloadRows(at: [IndexPath(item: index, section: theSection)], with: .fade)
table?.endUpdates()
}
}
import UIKit
final class MulticellTableHandler: BaseTableHandler<TableCellGenerator> {
public override func register(table: UITableView) {
super.register(table: table)
let cellTypes = self.collection.flatMap { $0.items.map { $0.identifier } }.map(TypeHolder.init)
Set(cellTypes).forEach { table.register($0.type, forCellReuseIdentifier: $0.name) }
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellBuilder = self.collection[indexPath.section].items[indexPath.item]
return cellBuilder.generate(tableView: tableView, for: indexPath)
}
private struct TypeHolder: Hashable {
let type: AnyClass
var name: String {
return String(describing: type)
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
static func == (lhs: TypeHolder, rhs: TypeHolder) -> Bool {
return lhs.name == rhs.name
}
}
}
import UIKit
protocol TableCellGenerator: class {
var identifier: UITableViewCell.Type { get }
func generate(tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell
func registerCell(in tableView: UITableView)
}
public protocol CellBuilder {
associatedtype CellType: UITableViewCell
func build(_ cell: CellType)
}
extension TableCellGenerator {
var reusableIdentifier: String {
return String(describing: identifier)
}
func registerCell(in tableView: UITableView) {
tableView.register(identifier, forCellReuseIdentifier: reusableIdentifier)
}
}
extension TableCellGenerator where Self: CellBuilder {
func generate(tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: reusableIdentifier, for: indexPath) as? Self.CellType else {
return UITableViewCell()
}
self.build(cell)
return cell
}
}
import UIKit
protocol TableCell: UITableViewCell {
associatedtype ViewModel
func update(with viewModel: ViewModel)
static var cellIdentifier: String { get }
static func registerCell(in tableview: UITableView)
}
extension TableCell {
static var cellIdentifier: String {
return String(describing: self)
}
static func registerCell(in table: UITableView) {
table.register(self, forCellReuseIdentifier: cellIdentifier)
}
}
import UIKit
final class UniversalTableHandler<T, Cell: TableCell> : BaseTableHandler<T> where Cell.ViewModel == T {
public override func register(table: UITableView) {
super.register(table: table)
Cell.registerCell(in: table)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: Cell.cellIdentifier) as? Cell else {
return UITableViewCell(style: .default, reuseIdentifier: Cell.cellIdentifier)
}
let item = self.collection[indexPath.section].items[indexPath.item]
cell.update(with: item)
return cell
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment