SourceModel 是对 UICollectionViewDataSource/Delegate
和 UITableViewDataSource/Delegate
的轻量级封装。它消除了使用 tableViews
和 collectionViews
时编写样板代码的负担,让您可以专注于重要的事情,即用数据填充单元格。
SourceModdel 专注于 关注点分离,其中单元格应只可供获取要呈现的数据并决定如何呈现。不应由 dataSource
控制单元格如何填充其数据,也不应了解视图。
使用方法
让我们跳入一些您需要熟悉的协议,以开始使用。让我们考虑一个显示产品的视图。
ModelCollection
协议
ModelCollection
代表一组相似的项目,在我们的例子中是产品。
public protocol ModelCollection {
/// Returns the number of items
var numberOfItems: Int { get }
/// Returns the number of sections
var numberOfSections: Int { get }
/// A subscript to return a type in an indexPath
subscript(indexPath: IndexPath) -> Model? { get }
/// A subscript to return a collection dataType within a section
subscript(section: Int) -> ModelCollection { get }
/// Returns the cell type at indexPath
func cellType(forItemAt indexPath: IndexPath) -> Fillable.Type?
}
Model
协议
Model
代表集合中的单个项,即产品。
两者结合使用
让我们创建一个项目集合来观察其作用
我们将在稍后的阶段深入研究
Fillable
协议,现在,让我们假设我们有一个ProductCell
。
struct Product { ... }
struct Products: ModelCollection {
let items: [Product]
var numberOfItems: Int {
return items.count
}
var numberOfSections: Int {
return 1
}
subscript(indexPath: IndexPath) -> Model? {
return items[indexPath.row]
}
subscript(section: Int) -> ModelCollection {
return self
}
func cellType(forItemAt indexPath: IndexPath) -> Fillable.Type? {
return ProductCell.self
}
}
好吧,这很酷,但我们还可以让它更简单。 SourceModel
提供了一个 Elements
类,它封装了上面的内容,你只需要处理单元格。这就是它是如何工作的
class Products: Elements<Product>, Codable {
func cellType(forItemAt indexPath: IndexPath) -> Fillable.Type? {
return ProductCell.self
}
}
现在我们完成了我们的集合,让我们创建一个 Products
的实例
let products = Products(items: [Product(), Product()])
Fillable
协议
每个单元格都必须遵循 Fillable
协议。这是您注入模型的地方,也是您填充单元格的地方。
class ProductCell: UICollectionViewCell, Fillable {
....
func fill(with model: Model?) {
guard let product = model as? Product else { return }
/// Handle product
}
}
准备数据源和代理
现在我们有了模型、模型集合和单元格,让我们创建 DataSource
和 Delegate
class ProductsViewController: UIViewController {
var dataSource: CollectionDataSource! /// Use TableDataSource for UITableView
var delegate: CollectionDelegate! /// Use TableDelegate for UITableView
let products = Products(items: [Product(), Product()])
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView(with: products)
}
private func setupCollectionView(with modelCollection: ModelCollection?)
delegate = CollectionDataSource(modelCollection: modelCollection)
dataSource = CollectionDelegate(modelCollection: modelCollection)
collectionView.register(cellType: ProductCell.self)
collectionView.delegate = delegate
collectionView.dataSource = dataSource
}
}
注意:要获取
register(cellType:)
函数,请在项目中添加 StanwoodCore。
就是这样!
你可能想知道接下来该做什么?让我们看看更复杂的案例,以及 SourceModel
如何帮助您。
Sections
处理 处理分区可能会是一个繁琐的任务。你可以使用 ModelCollection
并注入一组分区(其他 ModelCollection
)。或者,SourceModel
提供了 Sections。让我们看看我们之前使用的 Products
元素,并将其迁移到不同产品类别的分区中
class ProductsViewController: UIViewController {
var dataSource: CollectionDataSource! /// Use TableDataSource for UITableView
var delegate: CollectionDelegate! /// Use TableDelegate for UITableView
let sections: Sections!
override func viewDidLoad() {
super.viewDidLoad()
let beautyProducts = Products(items: [Product(), Product()])
let groceryProducts = Products(items: [Product(), Product()])
sections = Sections(sections: [beautyProducts, groceryProducts])
setupCollectionView(with: products)
}
private func setupCollectionView(with modelCollection: ModelCollection?)
delegate = CollectionDataSource(modelCollection: modelCollection)
dataSource = CollectionDelegate(modelCollection: modelCollection)
collectionView.register(cellType: ProductCell.self)
collectionView.delegate = delegate
collectionView.dataSource = dataSource
}
}
Headers
处理 如果您想要添加分区标题,请确保您的 ModelCollection
符合 Headerable
。您有一些可选的选项,具体取决于您是填充 collectionView
还是 tableView
。
Headerable
/// Headerable protocol to add header support for `UICollectionView` and `UITableView`
@objc public protocol Headerable {
/// `UITableView` section header view
@objc optional var headerView: UIView { get }
/// `UICollectionView` header reusable header view
@objc optional var reusableView: UICollectionReusableView { get }
}
示例
让我们给我们的产品
添加一个标题
class Products: Elements<Product>, Codable, Headerable {
var headerView: UIView {
let view = CategoryHeaderView.loadFromNib()
view?.backgroundColor = .clear
view?.set(title: title)
return view ?? UIView()
}
...
}
注意:要获取
loadFromNib()
函数,请将StanwoodCore
添加到您的项目中。[GitHub链接](https://github.com/stanwood/Stanwood_Core_iOS)
重要!
已知问题:在此阶段,
ModelCollection
不支持可重复使用的头部视图。这正在开发中。为了避免任何内存问题,您应该只对单个头部视图使用Headerable
。
Delegate
模式
实现为了使用源设计模式,源模型提供了一个Delegateable
协议。让我们看看它如何与我们的ProductCell
一起工作。
示例:单元格
protocol ProductCellDelegate: class {
func productCellDidDoSomething(with product: Product)
}
/// Conform to `Delegateable`
class ProductCell: UICollectionViewCell, Fillable, Delegateable {
private weak var delegate: ProductCellDelegate?
....
/// Set the delegate
func set(delegate: AnyObject) {
self.delegate = delegate as? ProductCellDelegate
}
@IBAction didTapSomeButton(_ sender: UIButton) {
delegate?. productCellDidDoSomething(with: product)
}
}
**示例:ViewController
class ProductsViewController: UIViewController {
var dataSource: CollectionDataSource! /// Use TableDataSource for UITableView
var delegate: CollectionDelegate! /// Use TableDelegate for UITableView
let products = Products(items: [Product(), Product()])
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView(with: products)
}
private func setupCollectionView(with modelCollection: ModelCollection?)
delegate = CollectionDataSource(modelCollection: modelCollection)
/// Inject `self` as the delegate
dataSource = CollectionDelegate(modelCollection: modelCollection, delegate: self)
collectionView.register(cellType: ProductCell.self)
collectionView.delegate = delegate
collectionView.dataSource = dataSource
}
}
extension ProductsViewController: ProductCellDelegate {
func productCellDidDoSomething(with product: Product) {
/// Do something with product
}
}
处理单元格索引
在某些情况下,我们需要知道单元格的位置索引。遵循Indexable
协议来获取索引。
示例
class ProductCell: UICollectionViewCell, Fillable, Indexable {
....
func inject(_ indexPath: IndexPath) {
/// Do something with the index
}
}
路线图
- 添加对
delegate
模式的支持 - 添加对
headers
的支持 - 为单元格注入
indexPath
添加Indexable
协议 - 添加
reloadData(animated:
)来重新加载已更改的模型 - 将泛型添加到
Fillable
协议中 - 添加可重复使用的
Header/Footer
视图 - 添加对
SwiftUI
的支持 - 添加对
Combine
的支持
请提交一个功能请求 issue。
示例
要运行示例项目,首先从 Git 仓库克隆,然后从 Example 目录运行pod install
。
要求
安装
可以通过CocoaPods获取SourceModel。要安装它,只需要将以下行添加到您的Podfile中
pod 'SourceModel'
作者
Tal Zion, [email protected]
授权
SourceModel可以使用MIT授权协议。有关更多信息,请参阅LICENSE文件。