DiffTableDirector
DiffTableDirector - 在 UITableViewDataSource & Delegate 之上的轻量级抽象。它允许您以声明式、类型安全的方式与表格一起工作。提供许多在原始表格视图中缺少的实用功能。
底层库使用 iOS 13 的原生 Diff API 和 iOS 11 & 12 的 DeppDiff。
特点
- 类型安全的泛型单元格、头部和尾部
- 自动注册表格元素
- 通过代理支持单元格/头部/尾部的动作
- 空的表格视图的可替换视图
- 观察表格边界交叉
- 使用您的加载器和动画轻松分页
- 在主线程或后台队列上通过 diff 更新表格
- 易于扩展
示例
要运行示例项目,请先克隆仓库,然后在 Example 目录中运行 pod install
用法
创建 TableDirector
import DiffTableKit
let tableDirector = TableDirector(tableView: awesomeTableView)
// or
let tableDirector = TableDirector()
// Later
tableDirector.connect(to: awesomeTableView)
支持单元格的可配置协议
import DiffTableKit
// MARK: - ConfigurableCell
extension SuperCell: ConfigurableCell {
struct ViewModel: Hashable {
let ID: String
let name: String
let icon: UIImage
}
func configure(_ item: ViewModel) {
_nameLabel.text = item.name
_iconImageView.set(image: item.icon)
}
}
// MARK: - ConfigurableHeaderFooter
extension SuperHeader: ConfigurableHeaderFooter {
typealias ViewModel = String
func configure(_ item: ViewModel) {
_titleLabel.text = item
}
}
填充您的表格
import DiffTableKit
let row = TableRow<SuperCell>(item: .init(ID: "uniqIdentifier", title: "Ttile", icon: UIImage(named: "icon"))
let header = TableHeader(item: "Header title")
let section = TableSection(rows: [row], headerConfigurator: header)
tableDirector.reload(with: [section])
你就是最好的。代码工作正常,表格已填充。简单,安全,所有项目均自动注册。但等等……如果按下其他单元格会发生什么?
操作
由代表执行的操作。你也可以使用回调达到相同的效果
extension SuperCell: ActionCell {
typealias ViewModel = InfoViewModel
typealias Delegate = CellPressableDelegate
func configure(_ item: InfoViewModel) {
// Fill your cell with data here
_titleLabel.text = item.title
contentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didPressedCell)))
}
@objc func didPressedCell() {
delegate?.didPressedCell(self)
}
}
创建单元格也将有些变化
let actionRow = TableActionRow<SuperCell>(viewModel: .init(ID: "uniqIdentifier", title: "Ttile", icon: UIImage(named: "icon"), delegate: self)
tableDirector.reload(with: [actionRow])
同样对于表头)
占位符视图
有时表格为空,看起来很丑。解决方案之一是为空状态添加漂亮的图片。让我们来实现它
import DiffTableKit
tableDirector.addEmptyStateView(viewFactory: { [unowned self] in
// Called on main thread
return PlaceholderView()
}, position: .center)
现在你可以看到当表格为空时占位符视图居中显示。当加载一些内容时,它就会消失。
差异
表格可以显示很多不同的信息。它可以单独提供。用动画方式更新表格,而不是多次重新加载。苹果已经提供了UIDiffableDataSource,但只适用于iOS 13。我们从它中得到灵感,以声明式方法提供相同解决方案。适用于iOS 11+)
所以我们需要做两个步骤
步骤1:为所有你的ViewModel实现Hashable协议。最好提供一些独一无二的ID,比如UUID().uuidString。
struct ViewModel: Hashable {
let id: String
...
}
TableDirector会在模型不可哈希时进行差异比较。但是同一个模型对于它来说是不同的。请记住这一点,并最好使用非可哈希的ViewModel来制作不可变的表格。
iOS 13中每次重新加载都将可比较。如果你提供了动画,你会看到动画。对于false情况,它将看起来像老式的reloadData。
你也可以手动触发可比较的重新加载(例如对于较低的iOS)。在主队列和你的队列上。
func reload(with sections: sections, reloadRule: .calculateSync) // Will calculate diff on main thread.
func reload(with sections: sections, reloadRule: .calculateAsync(queue: yourQueue) // Will calculate on queue your provider. Can prevent freeze for big collections
请注意:苹果建议仅在主队列或仅在后台队列上重新加载表格。错过队列可能导致未定义的行为。
跨界限
记得那个当你需要为滚动时的表DRAWmouseover阴影在上面的视图?还是也许 separator? 在滚动状态中隐藏它。
_tableDirector?.topCrossObserver = CrossObserver(didCross: {
// Draw your shadow/separator here
}, didReturn: {
// Hide shadow/separator here
})
底部边框也可以做到同样的效果。我们在其他项目中使用了它,而且真的很简单
分页和下拉刷新
顺便说一句。这种分页方式看起来不错!分页在业务逻辑中总是个头疼的问题。但如果你想要一些自定义行为或动画——UI也可能很棘手。因此我们提供了一个组件。它可以进行配置。可以控制视图的动画和生命周期。就像这样
let loader = Loader(view: viewForPagination, animator: yourAnimator)
let bottomPaginationController = PaginationController(
settings: .bottom,
loader: yourLoader) { (handler) in
network.request(...) {
// Change state of pagination. Also your can call same method on pagination controller
handler.finished(isSuccessfull: true, canLoadNext: true)
}
}
self._tableDirector?.add(paginationController: bottomPaginationController)
加载器是简单的结构
public struct Loader {
let view: UIView
let animator: PaginationControllerLoaderAnimator
}
为您的加载器提供动画的类应该符合 PaginationControllerLoaderAnimator。
public protocol PaginationControllerLoaderAnimator {
/// Animate loader base on state
/// - Parameter state: loader state
func animate(state: PaginationController.Loader.State)
}
/// Loader states
public enum State {
case initial
case loading
case error
case success
}
足以处理大多数情况。同样的方法也适用于下拉刷新。只需为 PaginationController 提供向上方向即可
要求
iOS 11.0+ Xcode 11.0+ Swift 5.0
安装
DiffTableDirector 通过 CocoaPods 提供。要安装,只需将以下行添加到您的 Podfile 中
pod 'DiffTableDirector'
作者
aleksiosdev, [email protected]
许可证
DiffTableDirector 在 Apache-2.0 许可证下提供。有关更多信息,请参阅 许可证文件