Atelier Clockwork

Swift Enumerations as Table View Data Sources

No More Index Counting

In Objective-C I’d often build table views using NS_ENUM and switch statements to cover all of the index paths. If there were no special cases, this worked and was fairly easy to go back to and understand later. That being said, at times I would hit special cases and end up with functions like adjustIndexPath(indexPath: NSIndexPath, forOptions options: TableOptions) -> NSIndexPath and have to make sure that function was called on every table view data source method.

Working in Swift, I’ve now taken a pass at making an easier to develop and maintain wrapper for table data.

I start out with some simple wrappers, which are specialized to whatever table view I’m working with to easily encapsulate all of the state required. As a simple contrived example:

struct TableSection {
    let title: String?
    let rows: [CellType]
}

enum CellType {
    case DecorativeCell
    case StringCell(String)
    case ImageCell(UIImage)
}

var sections: [TableSection]

This means the sections contain whatever data they require, and then the cell type can be used to control which cell shows up for any row in the table view.

This means that to update the sections in a table view, it would just require:

let images: [UIImage]
// Get images from somewhere

sections = [
    TableSection(title: "First Section",
        rows: [.DecorativeCell, .StringCell("Test")]),
    TableSection(title: "Image Section",
        rows: images.map(CellType.ImageCell)),
    TableSection(title: "Final Section",
        rows: [.StringCell("End"), .DecorativeCell])
]

This makes the order, and contents of the sections easy to work with. One particularly neat trick is using map to quickly wrap an entire array of an object type in the appropriate enum value.

Finally, the data source magic:

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return sections.count
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return sections[section].rows.count
}

func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return sections[section].title
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
    let cell: UITableViewCell
    switch sections[indexPath.section].rows[indexPath.row] {
    case .DecorativeCell:
        cell = decorativeCellForTableView(tableView, atIndexPath: indexPath)
    case .StringCell(let cellString):
        cell = stringCellForTableView(tableView, atIndexPath: indexPath, withString: cellString)
    case .ImageCell(let cellImage):
        cell = imageCellForTableView(tableView, atIndexPath: indexPath, withImage: cellImage)
    }
    return cell
}

func decorativeCellForTableView(tableView: UITableView, atIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    // dequeue and configure decorative cell
}

func stringCellForTableView(tableView: UITableView, atIndexPath indexPath: NSIndexPath, withString cellString: String) -> UITableViewCell {
    // dequeue and configure string cell
}

func imageCellForTableView(tableView: UITableView, atIndexPath indexPath: NSIndexPath, withImage cellImage: UIImage) -> UITableViewCell {
    // dequeue and configure image cell
}

The count functions now use array counts rather than having to derive counts from tricky hacks on enumeration values, and will always line up with the data, which is a nice plus and avoids having to do any math to adjust those based extra cells or skipped cells.

Getting to use a switch statement anywhere that the table view is working with a CellType is also a plus. This means as long as there isn’t a default case in the switch, adding a new type of cell will add warnings to every part of the code that’s handling cell types.

So far, it’s simplified making alterations to table views in custom code, and I find the code much easier to understand exactly what’s going on in a complex table.