Generic TableView DataSource
This is an experimental approach to writing a generic UITableViewDataSource
in Swift, that handles cell creation and the various boilerplate methods:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String?
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func numberOfSectionsInTableView(tableView: UITableView) -> Int
The idea is to avoid reimplementing the above methods for every UITableViewController
and to use a generic TableViewDataSource
instead. A typical use case is a ‘details’ view where multiple properties of a single value are displayed in a tableView (potentially using several kinds of tableViewCell). To support multiple types of cell, the requirement is to declare a data structure that represents the data for each type of cell in your TableView
. A simple example is shown below:
The Good…
For a TableView
with only one type of cell, the implementation is very simple:
struct CellInfo: SectionItem {
let cellID = "DetailCell"
let viewData: ViewDataProvider
}
class ViewController: UITableViewController {
let person = Person(name: "Fred Smith", age: 20, height: 176, weight: 65)
lazy var tableViewDataSource: TableViewDataSource<TableSection<CellInfo>> = {
return TableViewDataSource(sections: [
TableSection(title: "Details", items: [
CellInfo(viewData: DetailCell.ViewData(title: "Name:", detail: person.name)),
CellInfo(viewData: DetailCell.ViewData(title: "Age:", detail: person.age)),
CellInfo(viewData: DetailCell.ViewData(title: "Height:", detail: person.height)),
CellInfo(viewData: DetailCell.ViewData(title: "Weight:", detail: person.weight))
])
])
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = tableViewDataSource
}
}
The full implementation of the DetailCell
is as follows:
final class DetailCell: UITableViewCell, Cell {
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var detailLabel: UILabel!
struct ViewData: ViewDataProvider {
let title: String
let detail: String
init(title: String = "", detail: CustomStringConvertible = "") {
self.title = title
self.detail = detail.description
}
}
var viewData: ViewDataProvider = ViewData() {
didSet {
let data = viewData as! ViewData
titleLabel.text = data.title
detailLabel.text = data.detail
}
}
}
![screenshot] (http://alskipp.github.io/GenericDataSource/img/tableView1.png)
A data structure that represents several types of TableViewCell
can be written using an enum:
enum TableSectionItem: SectionItem {
case Header(viewData: HeadlineCell.ViewData)
case Detail(viewData: DetailCell.ViewData)
case Color(viewData: ColorCell.ViewData)
var cellID: String {
switch self {
case .Header: return CellID.HeadlineCell.rawValue
case .Detail: return CellID.DetailCell.rawValue
case .Color: return CellID.ColorCell.rawValue
}
}
var viewData: ViewDataProvider {
switch self {
case let .Header(viewData): return viewData as ViewDataProvider
case let .Detail(viewData): return viewData as ViewDataProvider
case let .Color(viewData): return viewData as ViewDataProvider
}
}
}
A full example of using a dataSource with multiple cell types is demonstrated in the Xcode project.
![screenshot] (http://alskipp.github.io/GenericDataSource/img/tableView2.png)
…The Bad & The Ugly
The current implementation works, but there is room for improvement. One aspect I'm unhappy with is the use of forced casts in a couple of places. One place where a forced cast is required is in any TableCell
which implements the Cell
protocol (take a look at the DetailCell
shown above). The viewData
var
needs to be cast from the protocol ViewDataProvider
to the specific type of ViewData
to access its properties – it's pretty ugly.
I'd be really interested to hear any ideas on how to improve matters, should you have a cunning plan, do let me know.
Inspiration
The initial idea came from this blog post by Joseph Lord. Additional ideas were gleaned from a talk by Andy Matuschak.