I’ve had a real drive to clean up my table view code when switching between cells and sections. I’ve been craving a data structure that keeps track of:
- The sections in a table view.
- The classes that are used in that section, and
- the reuse identifier.
A couple months ago I stumbled upon a solution to this problem when I found a new protocol in Swift 4 called CaseIterable
. I wrote this code to support some simple repeated logic in table view, which cleaned up a lot of code and made it a lot safer. An example:
enum CellSection: String, CaseIterable {
case overview = "Overview"
case schools = "School"
case jobs = "Job"
case skills = "Skill"
case projects = "Project"
case other = "Other"
}
Now by defining my sections as an string enum, I was able to clean up my data source code.
For instance, my numberOfSections(in:)
method became:
override func numberOfSections(in tableView: UITableView) -> Int {
return Section.allCases.count
}
This became safer than simply hardcoding the return value, as it changed with the enum case changes. Additionally, I’ve occasionally made the string in this enum both the ReuseIdentifier and the classname for the cell class. For example:
enum CellSection: String, CaseIterable {
case overview = “Resume.OverviewCell”
}
By doing this I can make my registration process more declarative.
tableView.register(NSClassForString(CellSection.overview.rawValue), forCellReuseIdentifier: CellSection.overview.rawValue)
In my tableView(_:cellForRowAt:)
method I started using:
let section = CellSection.allCases[indexPath.section]
let cell = tableView.dequeueReusableCell(withIdentifier: section.rawValue, for: indexPath)
Because CaseIterable returned the section in the order it was defined, my CellSection
enum declared the type of cells available in the order that appeared. This made my table view data source code more declarative. Additionally, I avoided a problem I’ve seen many times before where indexPath.section
is compared against a hardcoded integer in many spots in a data source. A good example implementation would be a sample tableView(_:numberOfRowsInSection:)
implementation:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let section = CellSection.allCases[section]
switch section {
case .overview:
return modelArray.count
}
}
Now, if I introduce a new section, I need to implement it in this switch. If I’m not going to use this section, I simply return 0. If Since switches in Swift must be exhaustive, I get an additional compile time check to ensure I haven’t implemented this data source improperly. If I decide I don’t need that safety I can use a default
case.
By adding that, I’ve cleaned up my table view code so that the table view’s sections are much more apparent and added so safety checks to prevent me from hanging myself later on.