The first step to creating an extended table view controller is subclassing. All of the functionality required to
display and edit sections and rows is built into EXTableViewController
. Therefore, simply create a new
subclass of EXTableViewController
.
class ViewController: EXTableViewController {}
The second step is to create the needed table view cells. Create a new table view cell, subclassing from
ConfigurableTableViewCell
. Implement the required protocols.
class IntTableViewCell: ConfigurableTableViewCell {
// 1
class var reuseIdentifier: String { return "IntTableViewCell" }
class var bundle: Bundle { return Bundle(for: IntTableViewCell.self) }
class var nib: UINib { return UINib(nibName: "IntTableViewCell", bundle: bundle) }
// 2
func configure(for value: Int) {
textLabel?.text = "Value"
detailTextLabel?.text = String(value)
}
}
- This allows cells to be easily dequeued. Make sure the reuse identifier matches the storyboard.
- This method will be called with a value to configure the cell. All configuration should be done here.
Next, the row must be created. This is the object that will represent a specific row in the table view.
struct IntRow: Row {
// 1
typealias DataType = Int
typealias CellType = IntTableViewCell
// 2
var data: Int
// 3
init(data: Int) {
self.data = data
}
}
- The typealiases are the associated types of the protocol, which fundamentally map a data type to a cell type. Now, whenever this row is used, and IntTableViewCell will automatically be configured with the
data
variable. Please note thatDataType
must be the same asCellType.DataType
. - This is the actual data stored in the row. For instance, this could be customer data, a string, name, etc. This is what will be displayed in the cell.
- This initializer will almost always be the same: simply set the
data
stored property.
The final step is to simply populate the table view with data, which is extremely simple. In the ViewController
class, simply override the generateSections
method. In the body of the method, generate the table view's sections and rows.
override func generateSections() -> [Section] {
// 1
var sections: [Section] = []
// 2
var section1 = Section()
section1.headerTitle = "Section 1"
// 3
section1.appendRow(IntRow(data: 1))
section1.appendRow(StringRow(data: "Awesome"))
section1.appendRow(IntRow(data: 22))
// 4
sections.append(section1)
var section2 = Section()
section2.headerTitle = "Section 2"
section2.appendRow(IntRow(data: 13))
// 5
section2.appendRow(StringRow(data: "So cool"))
section2.appendRow(StringRow(data: "Heck yeah"))
sections.append(section2)
return sections
}
- This is the array of sections that will be returned. All sections generated should be added to this array.
- This is how a new section is created. After this, you can add rows, change the header, etc.
- Simply initialize new rows (
IntRow(data: 1)
) and append them to the section. It's as easy as that. - Make sure to append the section to the array of sections
- Notice how you are able to append any type of row. Here, I added another type of row,
StringRow
, with no extra overhead.
Now run the project, and you have a fully functioning extended table view controller. Yay!
The traditional method of responding to user interraction is still valid. However, it has been optimized to be able to also be declarative. For instance, instead of having to use tableView(_:didSelectRowAt:)
, you can simply tell the row what to do when tapped using someRow.onDidSelect = ...
. However, this only works on rows that conform to responder protocols in EXTableViewController
. This happens because ExtendedTableViewController
checks for responders during each of the observable events.
UITableViewDelegate Method |
Responder Equivalent |
---|---|
tableView(_:willDisplay:forRowAt:) |
WillDisplayResponder.onWillDisplay |
tableView(_:willSelectRowAt:) |
WillSelectResponder.onWillSelect |
tableView(_:didSelectRowAt:) |
DidSelectResponder.onDidSelect |
tableView(_:willDeselectRowAt:) |
WillDeselectResponder.onWillDeselect |
tableView(_:didDeselectRowAt:) |
DidDeselectResponder.onDidDeselect |
Optionally, you can conform to FullResponder
to gain access to all methods.
struct IntRow: Row, DidSelectResponder {
...
var onDidSelect: ((IndexPath) -> ())?
}
let row = IntRow(data: 500)
row.onDidSelect = { _ in
print("Just tapped 500!")
}
Sometimes it may be required to do extra customization on a cell after it's initial configuration has been completed. This can be done by conforming your row to ConfigurableRow
, rather than Row
:
struct IntRow: ConfigurableRow {
...
var configuration: ((IntTableViewCell) -> ())?
}
Now, during configuration, the cell will automatically be passed into configuration
. By setting configuration
during section generation, the cell can be customized:
override func generateSections() -> [Section] {
...
let row = IntRow(data: 13)
row.configuration = { cell in
cell.textLabel?.textColor = .red
}
...
}
Though this functionality is available, it is recommended to only do this in situations that demand it. Displaying the data should be handled in the table view cell.
It is also very simple to display header and footer views in EXTableViewController
, and there are several ways to do so. The following list ranks the precidence of the options:
Section.headerView
orSection.footerView
- A single view to be displayed as the header/footer.Section.reusableHeaderViewClass
orSection.reusableFooterViewClass
- A reusable header/footer view class.EXTableViewController.defaultHeaderViewClass
orEXTableViewController.defaultFooterViewClass
- The default header/footer to be used in the table view, if one is not provided by the section.