ReusableDataSource 版本 0.1

ReusableDataSource 版本 0.1

Ivan Rep 维护。



  • 作者:
  • Ivan Rep

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)

就是这些!可重用数据源将管理单元格的创建和数据呈现。通过运行示例项目进行检查。

Demo project

安装

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")

ReusableTableViewDataSourceReusableCollectionViewDataSource完成了繁重的工作。初始化并将它们设置为UITableViewUICollectionView的数据源。

let dataSource = ReusableTableViewDataSource()

tableView.dataSource = dataSource

为了能够混合和匹配不同的视图模型,我们需要做一件被称为类型擦除的事情。看看这篇文章以了解其精髓Swift: Understanding Type Erasure

基本上,我们需要移除ReusableViewModel的泛型部分,以便将其放入数组中。

通过实现AnyTableViewPresentableViewModelAnyCollectionViewPresentableViewModel移除泛型部分,用于UITableViewCellUICollectionViewCell相应地。该过程与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文件。