A no-nonsense way to write cleaner UITableViewDelegate and UITableViewDataSource in Swift.

Overview

CascadingTableDelegate

CI Status Swift 5.0 Platform

Version Carthage compatible License

A no-nonsense way to write cleaner UITableViewDelegate and UITableViewDataSource.

Why is this library made?

In common iOS development, UITableView has became the bread and butter for building rich pages with repetitive elements. This page, for example:

Sample Page

(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.

Meet CascadingTableDelegate.

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 UITableViewDelegate and UITableViewDataSource for the UITableView.
    • Propagates almost all of delegate and dataSource calls to its childDelegates, based on section value of the passed IndexPath and the child's index.
    • Returns number of its childDelegates for numberOfSections(in:) call.
  • CascadingSectionTableDelegate:

    • Does not sets itself as UITableViewDelegate and UITableViewDataSource of the passed UITableView, but waits for its parentDelegate method calls.
    • Just like CascadingRootTableDelegate, it also propagates almost all of delegate and dataSource calls to its childDelegates, but based by the row of passed IndexPath.
    • Returns number of its childDelegates for tableView(_:numberOfRowsInSection:) call.

Here's a diagram to potray how a tableView(_:cellForRowAt:) call works to those classes:

Example Logic Diagram

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:

Section Delegates

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! 😁

Pros and Cons

Pros

With CascadingTableDelegate, we could:

  • Break down UITableViewDataSource and UITableViewDelegate methods to each section or row, resulting to cleaner, well separated code.
  • Use the familiar UITableViewDataSource and UITableViewDelegate methods that we have been used all along, allowing easier migrations for the old code.

Other pros:

  • All implemented methods on CascadingRootTableDelegate and CascadingSectionTableDelegate are unit tested! To run the tests, you could:
    • Open the sample project and run the available tests, or
    • Execute run_tests.sh in your terminal.
  • This library is available through Cocoapods and Carthage! 😉

Cons

1. Unpropagated special methods

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! 😁

2. tableView(_:estimatedHeightFor...:) method handlings

There are three optional UITableViewDelegate methods that used to estimate heights:

  • tableView(_:estimatedHeightForRowAt:),
  • tableView(_:estimatedHeightForHeaderInSection:), and
  • tableView(_: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.

3. weak declaration for parentDelegate

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 🙂

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

Requirements

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

Installation

Cocoapods

To install CascadingTableDelegate using CocoaPods, simply add the following line to your Podfile:

pod "CascadingTableDelegate", "~> 3.2"

Carthage

To install CascadingTableDelegate using Carthage, simply add the following line to your Cartfile:

github "edopelawi/CascadingTableDelegate" ~> 3.0

Author

Ricardo Pramana Suranta, [email protected]

License

CascadingTableDelegate is available under the MIT license. See the LICENSE file for more info.

You might also like...
Easy UITableView drag-and-drop cell reordering
Easy UITableView drag-and-drop cell reordering

SwiftReorder NOTE: Some users have encountered compatibility issues when using this library with recent versions of iOS. For apps targeting iOS 11 and

A cells of UITableView can be rearranged by drag and drop.
A cells of UITableView can be rearranged by drag and drop.

TableViewDragger This is a demo that uses a TableViewDragger. Appetize's Demo Requirements Swift 4.2 iOS 8.0 or later How to Install TableViewDragger

Protocol-oriented UITableView management, powered by generics and associated types.

DTTableViewManager Features Powerful mapping system between data models and cells, headers and footers Automatic datasource and interface synchronizat

Swift package for easily rendering text tables. Inspired by the Python tabulate library.

TextTable Easily print textual tables in Swift. Inspired by the Python tabulate library. Upcoming Changes See details on an upcoming change.

Simple static table views for iOS in Swift.
Simple static table views for iOS in Swift.

Simple static table views for iOS in Swift. Static's goal is to separate model data from presentation. Rows and Sections are your “view models” for yo

A Swift library for swipeable table cells
A Swift library for swipeable table cells

BWSwipeRevealCell Using the library **Note: Use version 1.0.1 for Swift 2.3 support and version 2.0.0 or higher for Swift 3 support ** There are two m

A pixel perfect replacement for UITableView section index, written in Swift
A pixel perfect replacement for UITableView section index, written in Swift

MYTableViewIndex MYTableViewIndex is a re-implementation of UITableView section index. This control is usually seen in apps displaying contacts, track

Swipeable UITableViewCell/UICollectionViewCell based on the stock Mail.app, implemented in Swift.
Swipeable UITableViewCell/UICollectionViewCell based on the stock Mail.app, implemented in Swift.

SwipeCellKit Swipeable UITableViewCell/UICollectionViewCell based on the stock Mail.app, implemented in Swift. About A swipeable UITableViewCell or UI

TableViews - Emoji Table View For iOS With Swift
TableViews - Emoji Table View For iOS With Swift

TableViews Hello! This is EmojiTableView. Let me introduce you my first app when

Comments
  • Fix Carthage Build Error

    Fix Carthage Build Error

    This fixes the Carthage build error experienced in Carthage version 0.26.2 (#16). A small change was made to the project build settings to use Xcode Default build directory instead of a target defined build directory.

    opened by leedsalex 4
  • InternalInconsistencyException fix for sample project

    InternalInconsistencyException fix for sample project

    This is the fix for issue #9. Some findings during fixing this:

    • leastNormalMagnitude will be treated as minus (hence the crash).
    • zero will make the TableView return the default height as header / footer.
    • anything between zero and one will be treated as minus.
    • one will make the TableView return the default height.
    • anything more than one (e.g 1.1) will set the header / footer to the actual height.
    opened by edopelawi 0
  • Change sample code's view models to implement Observer pattern

    Change sample code's view models to implement Observer pattern

    Instead of using a property closure that can only be set by an instance, Observer pattern will match the nature of the sample view models better. With it, those who tried the sample page could copy the section delegates and still see the section drawn properly :)

    opened by edopelawi 0
Owner
Ricardo Pramana Suranta
Quirky Software Engineer @ DANA Indonesia. Recent graduate of MS-SE student in CMU Silicon Valley.
Ricardo Pramana Suranta
A simple way to create a UITableView for settings in Swift.

QuickTableViewController A simple way to create a table view for settings, including: Table view cells with UISwitch Table view cells with center alig

Cheng-Yao Lin 525 Dec 20, 2022
A simpler way to do cool UITableView animations! (╯°□°)╯︵ ┻━┻

TableFlip (╯°□°)╯︵ ┻━┻ ┬──┬ ノ( ゜-゜ノ) Animations are cool. UITableView isn't. So why not make animating UITableView cool? The entire API for TableFlip

Joe Fabisevich 555 Dec 9, 2022
ClassicPhotos is a TableView App demos how to optimize image download and filter with operation queue.

ClassicPhotos ClassicPhotos is a TableView App demos how to optimize image download and filter with operation queue. With Operation and Operation Queu

Kushal Shingote 2 Dec 18, 2021
Display list of Marvel comic Characters and its detail view

Marvel Universe Display list of Marvel comic Characters and its detail view Installation Dependencies in this project are provided via Xcodegen (proje

null 1 Oct 19, 2021
APDynamicGrid is a SwiftUI package that helps you create consistent and animatable grids.

APDynamicGrid Overview APDynamicGrid is a SwiftUI package that helps you create consistent and animatable grids. The DynamicGrid View preserves the sa

Antonio Pantaleo 29 Jul 4, 2022
Use Yelp API to fetch restuarants around a location and show them in a table view

Yelp Use Yelp API to fetch restuarants around a location and show them in a table view - Infinite scrolling, Prefetching, Image Caching. Design Patter

null 0 Nov 1, 2021
A PageView, which supporting scrolling to transition between a UIView and a UITableView

YXTPageView ##A Page View, which support scrolling to transition between a UIView and a UITableView UIView (at the top) UITableView (at the bottom) In

Hanton Yang 68 May 25, 2022
a TableView have thumbnail cell only, and you can use gesture let it expands other expansionView, all diy

ZYThumbnailTableView #####可展开型预览TableView,开放接口,完全自由定制 #####An expandable preview TableView, custom-made all the modules completely with open API you c

null 950 Oct 17, 2022
Simple and beautiful stacked UIView to use as a replacement for an UITableView, UIImageView or as a menu

VBPiledView simple but highly effective animation and interactivity! By v-braun - viktor-braun.de. Preview Description Very simple and beautiful stack

Viktor Braun 168 Jan 3, 2023