Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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: 5 days ago
JSON representation
A no-nonsense way to write cleaner UITableViewDelegate and UITableViewDataSource in Swift.
- Host: GitHub
- URL: https://github.com/edopelawi/CascadingTableDelegate
- Owner: edopelawi
- License: mit
- Created: 2016-10-19T07:29:13.000Z (about 8 years ago)
- Default Branch: master
- Last Pushed: 2019-10-15T05:36:13.000Z (about 5 years ago)
- Last Synced: 2024-04-29T11:22:26.157Z (8 months ago)
- Topics: carthage, clean-code, cocoapods, ios, ios-swift, ios-ui, rich-pages, swift, uitableview, uitableviewdatasource, uitableviewdelegate
- Language: Swift
- Homepage:
- Size: 1.7 MB
- Stars: 909
- Watchers: 18
- Forks: 50
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
- awesome-ios - CascadingTableDelegate - A no-nonsense way to write cleaner UITableViewDelegate and UITableViewDataSource in Swift. (UI / Table View / Collection View)
- awesome-ios-star - CascadingTableDelegate - A no-nonsense way to write cleaner UITableViewDelegate and UITableViewDataSource in Swift. (UI / Table View / Collection View)
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.