{"id":2844,"url":"https://github.com/edopelawi/CascadingTableDelegate","last_synced_at":"2025-08-03T12:31:28.098Z","repository":{"id":56905691,"uuid":"71330583","full_name":"edopelawi/CascadingTableDelegate","owner":"edopelawi","description":"A no-nonsense way to write cleaner UITableViewDelegate and UITableViewDataSource in Swift.","archived":false,"fork":false,"pushed_at":"2019-10-15T05:36:13.000Z","size":1787,"stargazers_count":909,"open_issues_count":3,"forks_count":50,"subscribers_count":18,"default_branch":"master","last_synced_at":"2024-04-29T11:22:26.157Z","etag":null,"topics":["carthage","clean-code","cocoapods","ios","ios-swift","ios-ui","rich-pages","swift","uitableview","uitableviewdatasource","uitableviewdelegate"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/edopelawi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-10-19T07:29:13.000Z","updated_at":"2024-03-19T00:06:05.000Z","dependencies_parsed_at":"2022-08-20T19:20:26.849Z","dependency_job_id":null,"html_url":"https://github.com/edopelawi/CascadingTableDelegate","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edopelawi%2FCascadingTableDelegate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edopelawi%2FCascadingTableDelegate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edopelawi%2FCascadingTableDelegate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/edopelawi%2FCascadingTableDelegate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/edopelawi","download_url":"https://codeload.github.com/edopelawi/CascadingTableDelegate/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228543146,"owners_count":17934433,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["carthage","clean-code","cocoapods","ios","ios-swift","ios-ui","rich-pages","swift","uitableview","uitableviewdatasource","uitableviewdelegate"],"created_at":"2024-01-05T20:16:24.325Z","updated_at":"2024-12-07T00:31:00.559Z","avatar_url":"https://github.com/edopelawi.png","language":"Swift","funding_links":[],"categories":["UI"],"sub_categories":["Table View / Collection View","Layout","Other free courses"],"readme":"# CascadingTableDelegate\n\n[![CI Status](http://img.shields.io/travis/edopelawi/CascadingTableDelegate.svg?style=flat)](https://travis-ci.org/edopelawi/CascadingTableDelegate)\n[![Swift 5.0](https://img.shields.io/badge/Swift-5.0-brightgreen.svg)](https://swift.org)\n[![Platform](https://img.shields.io/cocoapods/p/CascadingTableDelegate.svg?style=flat)](http://cocoapods.org/pods/CascadingTableDelegate)\n\n[![Version](https://img.shields.io/cocoapods/v/CascadingTableDelegate.svg?style=flat)](http://cocoapods.org/pods/CascadingTableDelegate)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![License](https://img.shields.io/cocoapods/l/CascadingTableDelegate.svg?style=flat)](http://cocoapods.org/pods/CascadingTableDelegate)\n\n**A no-nonsense way to write cleaner `UITableViewDelegate` and `UITableViewDataSource`.**\n\n\n## Why is this library made?\n\nIn common iOS development, `UITableView` has became the bread and butter for building rich pages with repetitive elements. This page, for example:\n\n![Sample Page](ReadmeImages/sample-page-screenshot.jpg)\n\n(Kudos to [Wieky](https://id.linkedin.com/in/wiekyazza) for helping me creating this sample page's design! 😁)\n\nStill, using `UITableView` has its own problems.\n\nAs 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:)`.\n\nBecause 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.**\n\n# Meet CascadingTableDelegate.\n\n`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):\n\n```swift\n\npublic protocol CascadingTableDelegate: UITableViewDataSource, UITableViewDelegate {\n\n\t/// Index of this instance in its parent's `childDelegates`. Will be set by the parent.\n\tvar index: Int { get set }\n\n\t/// Array of child `CascadingTableDelegate` instances.\n\tvar childDelegates: [CascadingTableDelegate] { get set }\n\n\t/// Weak reference to this instance's parent `CascadingTableDelegate`.\n\tweak var parentDelegate: CascadingTableDelegate? { get set }\n\n\t/**\n\tBase initializer for this instance.\n\n\t- parameter index:          `index` value for this instance. May be changed later, including this instance's `parentDelegate`.\n\t- parameter childDelegates: Array of child `CascadingTableDelegate`s.\n\n\t- returns: This class' instance.\n\t*/\n\tinit(index: Int, childDelegates: [CascadingTableDelegate])\n\n\t/**\n\tPreparation method that will be called by this instance's parent, normally in the first time.\n\n\t- note: This method could be used for a wide range of purposes, e.g. registering table view cells.\n\t- note: If this called manually, it should call this instance child's `prepare(tableView:)` method.\n\n\t- parameter tableView: `UITableView` instance.\n\t*/\n\tfunc prepare(tableView tableView: UITableView)\n}\n\n```\n\nLong 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`.\n\n### But UITableViewDelegate and UITableViewDataSource has tons of methods! Who will propagate all those calls?\n\nWorry 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:\n\n- `CascadingRootTableDelegate`:\n\t- \tActs as the main `UITableViewDelegate` and `UITableViewDataSource` for the `UITableView`.\n\t-  Propagates **almost** all of delegate and dataSource calls to its `childDelegates`, based on `section` value of the passed `IndexPath` and the child's `index`.\n\t-  Returns number of its `childDelegates` for `numberOfSections(in:)` call.\n\n\n-  `CascadingSectionTableDelegate`:\n\t-  \tDoes not sets itself as `UITableViewDelegate` and `UITableViewDataSource` of the passed `UITableView`, but waits for its `parentDelegate` method calls.\n\t-  Just like `CascadingRootTableDelegate`, it also propagates **almost** all of delegate and dataSource calls to its `childDelegates`, but based by the `row` of passed `IndexPath`.\n\t-  Returns number of its `childDelegates` for `tableView(_:numberOfRowsInSection:)` call.\n\nHere's a diagram to potray how a `tableView(_:cellForRowAt:)` call works to those classes:\n\n\n![Example Logic Diagram](ReadmeImages/example-logic-diagram.jpg)\n\n\nBoth 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 😉\n\nHere's a snippet how the long page above is divided into section delegates in the sample code:\n\n![Section Delegates](ReadmeImages/section-delegates.jpg)\n\nAll 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! 😁\n\n## Pros and Cons\n\n### Pros\n\nWith CascadingTableDelegate, we could:\n\n- Break down `UITableViewDataSource` and `UITableViewDelegate` methods to each section or row, resulting to cleaner, well separated code.\n- Use the familiar `UITableViewDataSource` and `UITableViewDelegate` methods that we have been used all along, allowing easier migrations for the old code.\n\nOther pros:\n\n- **All implemented methods** on `CascadingRootTableDelegate` and `CascadingSectionTableDelegate` are unit tested! To run the tests, you could:\n\t-  Open the sample project and run the available tests, or\n\t-  Execute `run_tests.sh` in your terminal.\n- This library is available through Cocoapods and Carthage! 😉\n\n\n### Cons\n\n#### 1. Unpropagated special methods\n\nAs 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:\n\n - `sectionIndexTitles(for:)`\n - `tableView(_:sectionForSectionIndexTitle:at:)`\n - `tableView(_:moveRowAt:to:)`\n - `tableView(_:shouldUpdateFocusIn:)`\n - `tableView(_:didUpdateFocusInContext:with:)`\n - `indexPathForPreferredFocusedView(in:)`\n - `tableView(_:targetIndexPathForMoveFromRowAt: toProposedIndexPath:)`\n\n Should you need to implement any of those, feel free to subclass both of them and add your own implementations! 😁\n\n#### 2. `tableView(_:estimatedHeightFor...:)` method handlings\n\nThere are three optional `UITableViewDelegate` methods that used to estimate heights:\n\n- `tableView(_:estimatedHeightForRowAt:)`,\n- `tableView(_:estimatedHeightForHeaderInSection:)`, and\n- `tableView(_:estimatedHeightForFooterInSection:)`.\n\n`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.\n\nTo 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.\n\nShould both method not implemented by the `childDelegate`, `CascadingRootTableDelegate` and `CascadingSectionTableDelegate` will return `UITableViewAutomaticDimension` for `tableView(_:estimatedHeightForRowAt:)`, and `0` for `tableView(_:estimatedHeightForHeaderInSection:)` and `tableView(_:estimatedHeightForFooterInSection:)`.\n\nFor details of every method's default return value (that has one), please refer to the [Default Return Value documentation](Documentation/DefaultReturnValues.md).\n\n#### 3. `weak` declaration for `parentDelegate`\n\nSwift 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! 😁\n\nStill, 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 🙂\n\n## Example\n\nTo run the example project, clone the repo, and run `pod install` from the Example directory first.\n\n## Requirements\n\nBelow is the list of versions with its corresponding Swift version:\n\n| Swift Version | CascadingTableDelegate Version |\n| --- | --- \t\t|\n| 5.0 | 4.x\t \t|\n| 4.2 | 3.2.x \t|\n| 4.0 | 3.0.x \t|\n| 3.x | 2.x \t\t|\n| 2.2 | 1.x \t\t|\n\n## Installation\n\n### Cocoapods\n\nTo install CascadingTableDelegate using [CocoaPods](http://cocoapods.org), simply add the following line to your Podfile:\n\n```ruby\npod \"CascadingTableDelegate\", \"~\u003e 3.2\"\n```\n\n### Carthage\n\nTo install CascadingTableDelegate using [Carthage](https://github.com/Carthage/Carthage), simply add the following line to your Cartfile:\n\n```\ngithub \"edopelawi/CascadingTableDelegate\" ~\u003e 3.0\n```\n\n## Author\n\nRicardo Pramana Suranta, ricardo.pramana@gmail.com\n\n## License\n\nCascadingTableDelegate is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedopelawi%2FCascadingTableDelegate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fedopelawi%2FCascadingTableDelegate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fedopelawi%2FCascadingTableDelegate/lists"}