[iOS] UITableView 内对应复杂 DataSource(富文本内容)展现框架

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

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:

[iOS] UITableView 内对应复杂 DataSource(富文本内容)展现框架

(Kudos to Wieky

for helping me creating this sample page's design! :grin:

)

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:

[iOS] UITableView 内对应复杂 DataSource(富文本内容)展现框架

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 :wink:

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

[iOS] UITableView 内对应复杂 DataSource(富文本内容)展现框架

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! :grin:

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! :wink:

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! :grin:

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, the UITableView

will always

call those methods when rendering its rows, headers, and footers.

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

Somehow, Xcode won't add weak

modifier when you're implementing your own CascadingTableDelegate

and autocompleting the parentDelegate

property. Kindly add the weak

modifier manually to prevent retain cycles :grin:

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

This version supports Swift 3, which available in Xcode 8. For Swift 2.2 / Xcode 7.x support, kindly check the 1.x versions ��

Installation

Cocoapods

To install CascadingTableDelegate using CocoaPods

, simply add the following line to your Podfile:

pod "CascadingTableDelegate", "~> 2.0"

Carthage

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

github "edopelawi/CascadingTableDelegate" ~> 2.0

Author

Ricardo Pramana Suranta, ricardo.pramana@gmail.com

License

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

未登录用户
全部评论0
到底啦