A no-nonsense way to write cleaner UITableViewDelegate and UITableViewDataSource.
In common iOS development, UITableView has became the bread and butter for building rich pages with repetitive elements. This page, for example:
(Kudos to Wieky for helping me creating this sample page's design! π)
Still, using UITableView has its own problems.
As you know, to display the contents, UITableView uses UITableViewDelegate and UITableViewDataSource- compliant objects. This often became the cause of my headache since UITableView only allows one object to become the delegate and dataSource. These limitations might lead to an unnecessarily huge source code file - caused by know-it-all Megamoth methods. Some common victims of this problem are tableView(_:cellForRowAt:), tableView(_:heightForRowAt:), and tableView(_:didSelectRowAt:).
Because of this, there are times when I thought it be nice if we could split the delegate and dataSource method calls into each section or row.
CascadingTableDelegate is an approach to break down UITableViewDelegate and UITableViewDataSource into tree structure, inspired by the Composite pattern. Here's the simplified structure of the protocol (with less documentation):
public protocol CascadingTableDelegate: UITableViewDataSource, UITableViewDelegate {
	/// Index of this instance in its parent's `childDelegates`. Will be set by the parent.
	var index: Int { get set }
	/// Array of child `CascadingTableDelegate` instances.
	var childDelegates: [CascadingTableDelegate] { get set }
	/// Weak reference to this instance's parent `CascadingTableDelegate`.
	weak var parentDelegate: CascadingTableDelegate? { get set }
	/**
	Base initializer for this instance.
	- parameter index:          `index` value for this instance. May be changed later, including this instance's `parentDelegate`.
	- parameter childDelegates: Array of child `CascadingTableDelegate`s.
	- returns: This class' instance.
	*/
	init(index: Int, childDelegates: [CascadingTableDelegate])
	/**
	Preparation method that will be called by this instance's parent, normally in the first time.
	- note: This method could be used for a wide range of purposes, e.g. registering table view cells.
	- note: If this called manually, it should call this instance child's `prepare(tableView:)` method.
	- parameter tableView: `UITableView` instance.
	*/
	func prepare(tableView tableView: UITableView)
}Long story short, this protocol allows us to propagate any UITableViewDelegate or UITableViewDataSource method call it receives to its child, based on the section or row value of the passed IndexPath.
But UITableViewDelegate and UITableViewDataSource has tons of methods! Who will propagate all those calls?
Worry not, this library did the heavy lifting by creating two ready-to-use classes, CascadingRootTableDelegate and CascadingSectionTableDelegate. Both implements CascasdingTableDelegate protocol and the propagating logic, but with different use case:
- 
CascadingRootTableDelegate:- Acts as the main 
UITableViewDelegateandUITableViewDataSourcefor theUITableView. - Propagates almost all of delegate and dataSource calls to its 
childDelegates, based onsectionvalue of the passedIndexPathand the child'sindex. - Returns number of its 
childDelegatesfornumberOfSections(in:)call. 
 - Acts as the main 
 - 
CascadingSectionTableDelegate:- Does not sets itself as 
UITableViewDelegateandUITableViewDataSourceof the passedUITableView, but waits for itsparentDelegatemethod calls. - Just like 
CascadingRootTableDelegate, it also propagates almost all of delegate and dataSource calls to itschildDelegates, but based by therowof passedIndexPath. - Returns number of its 
childDelegatesfortableView(_:numberOfRowsInSection:)call. 
 - Does not sets itself as 
 
Here's a diagram to potray how a tableView(_:cellForRowAt:) call works to those classes:
Both classes also accepts your custom implementations of CascadingTableDelegate (which is only UITableViewDataSource and UITableViewDelegate with few new properties and methods, really) as their childDelegates. Plus, you could subclass any of them and call super on the overriden methods to let them do the propagation - Chain-of-responsibility-esque style π
Here's a snippet how the long page above is divided into section delegates in the sample code:
All the section delegate classes then added as childs to a single CascadingRootTableDelegate. Any change on the sequence or composition of its childDelegates will affect the displayed table. Clone this repo and try it out in sample project! π
With CascadingTableDelegate, we could:
- Break down 
UITableViewDataSourceandUITableViewDelegatemethods to each section or row, resulting to cleaner, well separated code. - Use the familiar 
UITableViewDataSourceandUITableViewDelegatemethods that we have been used all along, allowing easier migrations for the old code. 
Other pros:
- All implemented methods on 
CascadingRootTableDelegateandCascadingSectionTableDelegateare unit tested! To run the tests, you could:- Open the sample project and run the available tests, or
 - Execute 
run_tests.shin your terminal. 
 - This library is available through Cocoapods and Carthage! π
 
As you know, not all UITableViewDelegate method uses single IndexPath as their parameter, which makes propagating their calls less intuitive. Based on this reasoning, CascadingRootTableDelegate and CascadingSectionTableDelegate doesn't implement these UITableViewDelegate methods:
sectionIndexTitles(for:)tableView(_:sectionForSectionIndexTitle:at:)tableView(_:moveRowAt:to:)tableView(_:shouldUpdateFocusIn:)tableView(_:didUpdateFocusInContext:with:)indexPathForPreferredFocusedView(in:)tableView(_:targetIndexPathForMoveFromRowAt: toProposedIndexPath:)
Should you need to implement any of those, feel free to subclass both of them and add your own implementations! π
There are three optional UITableViewDelegate methods that used to estimate heights:
tableView(_:estimatedHeightForRowAt:),tableView(_:estimatedHeightForHeaderInSection:), andtableView(_:estimatedHeightForFooterInSection:).
CascadingRootTableDelegate and CascadingSectionTableDelegate implements those calls for propagating it to the childDelegates. And since both of them implements those methods, they will allow UITableView to always call those methods to every childDelegates, should they found any of their child implements those methods.
To prevent layout breaks, CascadingRootTableDelegate and CascadingSectionTableDelegate will call its childDelegate's tableView(_:heightFor...:) counterpart for the unimplemented methods, so the UITableView will render it correctly. If your tableView(_:heightFor...:) methods use heavy calculations, it is advised to implement the tableView(_:estimatedHeightFor...:) counterpart of them.
Should both method not implemented by the childDelegate, CascadingRootTableDelegate and CascadingSectionTableDelegate will return UITableViewAutomaticDimension for tableView(_:estimatedHeightForRowAt:), and 0 for tableView(_:estimatedHeightForHeaderInSection:) and tableView(_:estimatedHeightForFooterInSection:).
For details of every method's default return value (that has one), please refer to the Default Return Value documentation.
Swift won't allow us to add weak modifier in protocols, but we need it for CascadingTableDelegate's parentDelegate property. Kindly add the weak modifier manually in the front of parentDelegate property of your CascasdingTableDelegate-compliant class to prevent retain cycles! π
Still, if you still think typing it manually is a tedious job, just subclass the CascadingBareTableDelegate out. It's a bare implementation of the CascadingTableDelegate, without the propagating logic π
To run the example project, clone the repo, and run pod install from the Example directory first.
Below is the list of versions with its corresponding Swift version:
| Swift Version | CascadingTableDelegate Version | 
|---|---|
| 5.0 | 4.x | 
| 4.2 | 3.2.x | 
| 4.0 | 3.0.x | 
| 3.x | 2.x | 
| 2.2 | 1.x | 
To install CascadingTableDelegate using CocoaPods, simply add the following line to your Podfile:
pod "CascadingTableDelegate", "~> 3.2"To install CascadingTableDelegate using Carthage, simply add the following line to your Cartfile:
github "edopelawi/CascadingTableDelegate" ~> 3.0
Ricardo Pramana Suranta, [email protected]
CascadingTableDelegate is available under the MIT license. See the LICENSE file for more info.


