CascadingTableDelegate
以简洁的方式编写更清洁的 UITableViewDelegate
和 UITableViewDataSource
。
为什么创建这个库?
在常见的 iOS 开发中,UITableView
已经成为用于构建具有重复元素丰富页面的主流。例如,这个页面
(感谢 Wieky 帮助我设计这个示例页面!)
然而,使用 UITableView
也有其问题。
正如您所知,为了显示内容,UITableView
使用与 UITableViewDelegate
和 UITableViewDataSource
兼容的对象。由于 UITableView
只允许一个对象成为 delegate
和 dataSource
,这往往是让我头疼的原因。这些限制可能导致代码文件变得非常大——由于“巨眼方法”所致。这个问题的常见受害者包括 tableView(_:cellForRowAt:)
、tableView(_:heightForRowAt:)
和 tableView(_:didSelectRowAt:)
。
正因为如此,有时我会想,如果可以的话,将 delegate
和 dataSource
方法调用 拆分到每个部分或行 会很方便。
了解CascadingTableDelegate。
CascadingTableDelegate
是一种将 UITableViewDelegate
和 UITableViewDataSource
分解成树形结构的方法,灵感来源于 组合模式。以下是协议的简化结构(文档较少)
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)
}
简而言之,此协议允许我们将接收到的任何 UITableViewDelegate
或 UITableViewDataSource
方法的调用根据传递的 IndexPath
的 section
或 row
值传递给其子级。
但是,UITableViewDelegate 和 UITableViewDataSource 有很多方法!谁来传播所有这些调用呢?
不必担心,这个库通过创建两个可立即使用的类来完成这项繁重的工作,即 两个类,CascadingRootTableDelegate
和 CascadingSectionTableDelegate
。这两个类都实现了 CascadingTableDelegate
协议和传播逻辑,但有不同的用途
-
CascadingRootTableDelegate
:- 充当
UITableView
的主要UITableViewDelegate
和UITableViewDataSource
。 - 根据传递的
IndexPath
的section
值和子级的index
,将几乎所有的代理和数据源调用传递到其childDelegates
上。 - 对于
numberOfSections(in:)
调用,返回它的childDelegates
的数量。
- 充当
-
CascadingSectionTableDelegate
:- 不会将其自身设置为传递的
UITableView
的UITableViewDelegate
和UITableViewDataSource
,而是等待其parentDelegate
方法的调用。 - 就像
CascadingRootTableDelegate
一样,它也将几乎所有代理和数据源调用传播到其childDelegates
上,但根据传递的IndexPath
的row
。 - 对于
tableView(_:numberOfRowsInSection:)
调用,返回它的childDelegates
的数量。
- 不会将其自身设置为传递的
这是一张图,说明了 tableView(_:cellForRowAt:)
调用如何作用到这些类上
这两个类也接受您自定义的 CascadingTableDelegate
实现作为它们的 childDelegates
(实际上只有一个用来添加一些新属性和方法的 UITableViewDataSource
和 UITableViewDelegate
)。此外,您可以对该中的任何一个进行子类化,并调用重写方法中的 super
以让它们执行传播 - 类似于 责任链 的风格
以下是上面长页如何分割成段落在示例代码中的片段
然后,将所有部分代理类添加到单个 CascadingRootTableDelegate
中。其 childDelegates
的序列或组成中的任何更改都将影响显示的表。克隆此存储库,并在示例项目中尝试它!
优点与缺点
优点
在使用CascadingTableDelegate后,我们可以
- 将
UITableViewDataSource
和UITableViewDelegate
的方法细化到每个分区或行,从而得到更清洁、更隔离的代码。 - 使用熟悉的
UITableViewDataSource
和UITableViewDelegate
方法,这使代码迁移更加容易。
其他优点
- 所有实现的方法在
CascadingRootTableDelegate
和CascadingSectionTableDelegate
上都经过单元测试!要运行测试,您可以- 打开示例项目并运行可用的测试,或
- 在终端中执行
run_tests.sh
。
- 本库可通过Cocoapods和Carthage获得!
😉
缺点
1. 未传播的特殊方法
正如您所知,并非所有的UITableViewDelegate
方法都使用单个IndexPath
作为它们的参数,这使得调用传播不太直观。基于这个原因,CascadingRootTableDelegate
和CascadingSectionTableDelegate
没有实现这些UITableViewDelegate
方法
sectionIndexTitles(for:)
tableView(_:sectionForSectionIndexTitle:at:)
tableView(_:moveRowAt:to:)
tableView(_:shouldUpdateFocusIn:)
tableView(_:didUpdateFocusInContext:with:)
indexPathForPreferredFocusedView(in:)
tableView(_:targetIndexPathForMoveFromRowAt: toProposedIndexPath:)
如果您需要实现其中任何一种,请自由继承这两种方法并添加您自己的实现!
tableView(_:estimatedHeightFor...
方法处理
2. 有三个可选的UITableViewDelegate
方法用于估计高度
tableView(_:estimatedHeightForRowAt:)
,tableView(_:estimatedHeightForHeaderInSection:)
,以及tableView(_:estimatedHeightForFooterInSection:)
.
CascadingRootTableDelegate
和 CascadingSectionTableDelegate
实现了这些调用,以便将其传播到 childDelegates
。由于它们都实现了这些方法,因此将允许 UITableView
一直调用这些方法,针对每一个 childDelegates
,如果它们发现了任何实现这些方法的子项。
为了防止布局损坏,CascadingRootTableDelegate
和 CascadingSectionTableDelegate
将调用子代理的 tableView(_:heightFor...:)
对应方法来占位不实现的方法,这样 UITableView
将正确地渲染它。如果你的 tableView(_:heightFor...:)
方法使用了大量的计算,建议实现它们的 tableView(_:estimatedHeightFor...:)
对应方法。
如果两个方法都没有被 childDelegate
实现,CascadingRootTableDelegate
和 CascadingSectionTableDelegate
将为 tableView(_:estimatedHeightForRowAt:)
返回 UITableViewAutomaticDimension
,而为 tableView(_:estimatedHeightForHeaderInSection:)
和 tableView(_:estimatedHeightForFooterInSection:)
返回 0
。
有关每个方法默认返回值(如有)的详细信息,请参阅 默认返回值文档。
parentDelegate
添加 weak
声明
3. 为 Swift 不允许我们在协议中使用 weak
修饰符,但我们需要在 CascadingTableDelegate
的 parentDelegate
属性中。请手动在您符合 CascasdingTableDelegate
的类中 parentDelegate
属性的前面添加 weak
修饰符,以防止引用循环!
仍然,如果您认为手动输入是一项繁琐的工作,则只需从 CascadingBareTableDelegate
继承。它是对 CascadingTableDelegate
的裸实现,不包含传播逻辑。
示例
要运行示例项目,请先克隆存储库,然后首先从示例目录运行 pod install
。
需求
以下是版本列表及其对应的 Swift 版本
Swift 版本 | CascadingTableDelegate 版本 |
---|---|
5.0 | 4.x |
4.2 | 3.2.x |
4.0 | 3.0.x |
3.x | 2.x |
2.2 | 1.x |
安装
Cocoapods
要使用 CocoaPods 安装 CascadingTableDelegate,只需在 Podfile 中添加以下行:
pod "CascadingTableDelegate", "~> 3.2"
Carthage
要使用 Carthage 安装 CascadingTableDelegate,只需在 Cartfile 中添加以下行:
github "edopelawi/CascadingTableDelegate" ~> 3.0
作者
By Ricardo Pramana Suranta,[email protected]
许可
CascadingTableDelegate 在 MIT 许可下可用。有关更多信息,请参阅 LICENSE 文件。