ReusableDataSource
不再需要编写自定义的 UITableView 或 UICollectionView 数据源。免责声明:并非真的如此,但这是一个良好的开始。
实现
在可重用视图上实现 ReusablePresenter
协议。
class TextTableViewCell: UITableViewCell, ReusablePresenter {
func present(viewModel: String) {
textLabel?.text = viewModel
}
}
通过使用 ReusableViewModel
结构体创建视图模型并指定呈现器类型。
let viewModels = [
ReusableViewModel<TextTableViewCell>(viewModel: "Cell 1").anyPresentable,
ReusableViewModel<TextTableViewCell>(viewModel: "Cell 2").anyPresentable,
ReusableViewModel<ImageTextTableViewCell>(
viewModel: ImageTextTableViewCellViewModel(
textViewModel: "Cell 3",
imageViewModel: #imageLiteral(resourceName: "filter")
)
).anyPresentable,
ReusableViewModel<TextTableViewCell>(viewModel: "Cell 2").anyPresentable
]
创建一个 ReusableTableViewDataSource
并呈现视图模型。
let dataSource = ReusableTableViewDataSource()
tableView.dataSource = dataSource
dataSource.present(presentableViewModels: reusableViewModels, on: tableView)
就是这些!可重用数据源将管理单元格的创建和数据呈现。通过运行示例项目进行检查。
安装
Carthage
github "Rep2/ReusableDataSource" ~> 0.1
CocoaPods
pod 'ReusableDataSource', '~> 0.1'
详细概述
ReusablePresenter
定义了可以呈现的视图模型类型。
protocol ReusablePresenter {
associatedtype ViewModel
static var source: ReusablePresenterSource { get }
func present(viewModel: ViewModel)
}
为了满足此协议,只需要实现present(viewModel: ViewModel)
函数。associatedtype
是从函数中推断出来的。
source
值决定了可重用视图的创建方式。它的默认值是class
。目前可以忽略它。
enum ReusablePresenterSource {
case nib
case `class`
case storyboard
}
ReusableViewModel
连接了呈现实体和呈现实实体知道的视图模型。
struct ReusableViewModel<Presenter: ReusablePresenter> {
let viewModel: Presenter.ViewModel
init(viewModel: Presenter.ViewModel) {
self.viewModel = viewModel
}
}
只需通过指定ReusablePresenter
类型并传递关联的ViewModel
来创建它。
ReusableViewModel<TextTableViewCell>(viewModel: "Cell 1")
ReusableTableViewDataSource
和ReusableCollectionViewDataSource
完成了繁重的工作。初始化并将它们设置为UITableView
或UICollectionView
的数据源。
let dataSource = ReusableTableViewDataSource()
tableView.dataSource = dataSource
为了能够混合和匹配不同的视图模型,我们需要做一件被称为类型擦除
的事情。看看这篇文章以了解其精髓Swift: Understanding Type Erasure。
基本上,我们需要移除ReusableViewModel
的泛型部分,以便将其放入数组中。
通过实现AnyTableViewPresentableViewModel
和AnyCollectionViewPresentableViewModel
移除泛型部分,用于UITableViewCell
和UICollectionViewCell
相应地。该过程与Swift Stdlib处理类型擦除
的方式相似。我从Type-erasure in Stdlib中获得了灵感。
class AnyTableViewPresentableViewModel {
let dequeueAndPresentCellCallback: (UITableView) -> UITableViewCell
let registerCellCallback: (UITableView) -> Void
init<Presenter: ReusablePresenter>(base: ReusableViewModel<Presenter>) where Presenter: UITableViewCell {
self.dequeueAndPresentCellCallback = { (tableView: UITableView) -> UITableViewCell in
tableView.dequeueAndPresent(presentableViewModel: base, for: IndexPath(item: 0, section: 0))
}
self.registerCellCallback = { (tableView: UITableView) in
tableView.register(cell: Presenter.self, reusableCellSource: Presenter.source)
}
}
}
它们移除了ReusableViewModel
的泛型部分,并保留了我们需要的信息——>如何注册和排序列表项。
ReusableViewModel
的属性anyPresentable
可用于简化过程。
extension ReusableViewModel where Presenter: UITableViewCell {
var anyPresentable: AnyTableViewPresentableViewModel {
return AnyTableViewPresentableViewModel(base: self)
}
}
extension ReusableViewModel where Presenter: UICollectionViewCell {
var anyPresentable: AnyCollectionViewPresentableViewModel {
return AnyCollectionViewPresentableViewModel(base: self)
}
}
最后,将类型擦除
的视图模型传递给可重用数据源。
dataSource.present(presentableViewModels: viewModels, on: tableView)
可重用数据源使用PresentableViewModel
的排序列表项方法创建适当类型的单元格并呈现视图模型。
extension UITableView {
/**
Returns a "cell" on which `presentableViewModel` was presented.
- Important: Causes the app to crashes with `NSInternalInconsistencyException` if the `PresentingCell` type isn't previously registered.
*/
func dequeueAndPresent<Presenter: ReusablePresenter>(presentableViewModel: ReusableViewModel<Presenter>, for indexPath: IndexPath) -> Presenter
where Presenter: UITableViewCell {
let cell = dequeueReusableCell(for: indexPath) as Presenter
cell.present(viewModel: presentableViewModel.viewModel)
return cell
}
/**
Registers a reusable "cell" using `CustomStringConvertible` as the reuese identifier.
- Important: Call before `dequeueReusableCell(for:)` to avoid `NSInternalInconsistencyException`.
*/
public func register<T: UITableViewCell>(cell: T.Type, reusableCellSource: ReusablePresenterSource) {
switch reusableCellSource {
case .nib:
register(UINib(nibName: String(describing: cell), bundle: nil), forCellReuseIdentifier: String(describing: cell))
case .class, .storyboard:
register(T.self, forCellReuseIdentifier: String(describing: cell.self))
}
}
/**
Returns a "cell" of a given type using `CustomStringConvertible` as the reuese identifier.
- Important: Force unwraps the "cell". Causes the app to crashes with `NSInternalInconsistencyException` if the cell type isn't previously registered.
*/
public func dequeueReusableCell<T: UITableViewCell>(for indexPath: IndexPath) -> T {
return dequeueReusableCell(for: indexPath)!
}
/**
Returns an optional "cell" of a given type using `CustomStringConvertible` as the reuese identifier.
- Important: Causes the app to crashes with `NSInternalInconsistencyException` if the cell type isn't previously registered.
*/
public func dequeueReusableCell<T: UITableViewCell>(for indexPath: IndexPath) -> T? {
return dequeueReusableCell(withIdentifier: String(describing: T.self), for: indexPath) as? T
}
}
这也是ReusablePresenterSource
发挥作用的地方。数据源将自动根据ReusablePresenter.source
属性注册reuseIdentifier
。要禁用此行为,请将数据源automaticallyRegisterReuseIdentifiers
设置为false
。
许可证
可重用数据源适用于MIT许可证。有关更多信息,请参阅LICENSE文件。