Last active
August 12, 2022 17:33
-
-
Save andreashanft/e9b99e4dbe5c87f8914e52585689d16e to your computer and use it in GitHub Desktop.
Simple Generic UITableView Data Source
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
// | |
// GenericTableManager.swift | |
// Copyright © 2020 andreashanft.de. All rights reserved. | |
// | |
import Foundation | |
import UIKit | |
import Reusable | |
/* | |
class SomeTableViewCell: UITableViewCell, Reusable {} | |
final class SomeTableViewModel { | |
@Published private(set) var items = ["Hello World", "Lorem Ipsum", "Don't Picknick"] | |
} | |
class SomeTableViewController: UIViewController { | |
private lazy var dataSource = | |
SingleSectionDataSource<String, SomeTableViewCell>(tableView) { (element, cell, indexPath) in | |
cell.textLabel?.text = element | |
} | |
private lazy var tableView = UITableView() | |
private var subscriptions = Set<AnyCancellable>() | |
private let viewModel: SomeTableViewModel | |
private func setupBindings() { | |
subscriptions.add([ | |
viewModel.$items.assign(to: \.items, on: dataSource) | |
]) | |
} | |
} | |
*/ | |
protocol SectionDefining { | |
associatedtype E | |
var headerTitle: String? { get } | |
var footerTitle: String? { get } | |
var elements: [E] { get } | |
init(elements: [E]) | |
} | |
struct GenericSection<E>: SectionDefining { | |
var headerTitle: String? { nil } | |
var footerTitle: String? { nil } | |
var elements = [E]() | |
} | |
typealias ReusableTableViewCell = UITableViewCell & Reusable | |
// MARK: - SingleSectionDataSource | |
class SingleSectionDataSource<E, C: ReusableTableViewCell>: GenericDataSource<GenericSection<E>, C> { | |
public var items: [ElementType] { | |
set { | |
let section = SectionType(elements: newValue) | |
sections = [section] | |
} | |
get { | |
guard let items = sections.first?.elements else { | |
return [] | |
} | |
return items | |
} | |
} | |
} | |
// MARK: - GenericDataSource | |
class GenericDataSource<S, C>: NSObject, UITableViewDataSource, UITableViewDelegate where S: SectionDefining, C: ReusableTableViewCell { | |
public typealias CellConfig<Element, Cell> = (Element, Cell, IndexPath) -> Void | |
public var sections = [S]() { | |
didSet { | |
tableView.reloadData() | |
} | |
} | |
typealias ElementType = S.E | |
typealias SectionType = S | |
private let tableView: UITableView | |
private var cellFactory: ((UITableView, IndexPath, ElementType) -> C) | |
init(_ tableView: UITableView, cellConfig: @escaping CellConfig<ElementType, C>) { | |
self.tableView = tableView | |
self.cellFactory = { tableView, indexPath, element in | |
let cell: C = tableView.dequeueReusableCell(for: indexPath) | |
cellConfig(element, cell, indexPath) | |
return cell | |
} | |
super.init() | |
tableView.register(cellType: C.self) | |
tableView.dataSource = self | |
} | |
func numberOfSections(in tableView: UITableView) -> Int { | |
sections.count | |
} | |
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { | |
sections[section].elements.count | |
} | |
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { | |
sections[section].headerTitle | |
} | |
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { | |
sections[section].footerTitle | |
} | |
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { | |
let element = sections[indexPath.section].elements[indexPath.row] | |
return cellFactory(tableView, indexPath, element) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment