Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/edopelawi/CascadingTableDelegate

A no-nonsense way to write cleaner UITableViewDelegate and UITableViewDataSource in Swift.
https://github.com/edopelawi/CascadingTableDelegate

carthage clean-code cocoapods ios ios-swift ios-ui rich-pages swift uitableview uitableviewdatasource uitableviewdelegate

Last synced: 3 months ago
JSON representation

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

Awesome Lists containing this project

README

        

# CascadingTableDelegate

[![CI Status](http://img.shields.io/travis/edopelawi/CascadingTableDelegate.svg?style=flat)](https://travis-ci.org/edopelawi/CascadingTableDelegate)
[![Swift 5.0](https://img.shields.io/badge/Swift-5.0-brightgreen.svg)](https://swift.org)
[![Platform](https://img.shields.io/cocoapods/p/CascadingTableDelegate.svg?style=flat)](http://cocoapods.org/pods/CascadingTableDelegate)

[![Version](https://img.shields.io/cocoapods/v/CascadingTableDelegate.svg?style=flat)](http://cocoapods.org/pods/CascadingTableDelegate)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![License](https://img.shields.io/cocoapods/l/CascadingTableDelegate.svg?style=flat)](http://cocoapods.org/pods/CascadingTableDelegate)

**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](ReadmeImages/sample-page-screenshot.jpg)

(Kudos to [Wieky](https://id.linkedin.com/in/wiekyazza) 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](https://blog.codinghorror.com/new-programming-jargon/). 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](https://en.wikipedia.org/wiki/Composite_pattern). Here's the simplified structure of the protocol (with less documentation):

```swift

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](ReadmeImages/example-logic-diagram.jpg)

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](https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern)-esque style 😉

Here's a snippet how the long page above is divided into section delegates in the sample code:

![Section Delegates](ReadmeImages/section-delegates.jpg)

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](Documentation/DefaultReturnValues.md).

#### 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](http://cocoapods.org), simply add the following line to your Podfile:

```ruby
pod "CascadingTableDelegate", "~> 3.2"
```

### Carthage

To install CascadingTableDelegate using [Carthage](https://github.com/Carthage/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.