Swift 中具有不同单元格类型的可重用数据源
Reusable data sources in Swift w/ different cell types
基于此 article 我为 UICollectionView
创建了一个可重用的数据源,如下所示:-
final class CollectionViewDataSource<Model>: NSObject, UICollectionViewDataSource {
typealias CellConfigurator = (Model, UICollectionViewCell) -> Void
var models: [Model] = []
private let reuseIdentifier: String
private let cellConfigurator: CellConfigurator
init(reuseIdentifier: String, cellConfigurator: @escaping CellConfigurator) {
self.reuseIdentifier = reuseIdentifier
self.cellConfigurator = cellConfigurator
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return models.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let model = models[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
cellConfigurator(model, cell)
return cell
}
}
然后我扩展了这个 class 所以我可以提供 'cell specific' 基于模型类型的设置
extension CollectionViewDataSource where Model == HomeFeedItem {
static func make(reuseIdentifier: String = "FEED_ARTICLE_CELL") -> CollectionViewDataSource {
return CollectionViewDataSource(reuseIdentifier: reuseIdentifier, cellConfigurator: { item, cell in
(cell as? FeedArticleCell)?.render(with: item)
})
}
}
extension CollectionViewDataSource where Model == HomeFeedAlertItem {
static func make(reuseIdentifier: String = "FEED_ALERT_CELL") -> CollectionViewDataSource {
return CollectionViewDataSource(reuseIdentifier: reuseIdentifier, cellConfigurator: { item, cell in
(cell as? FeedAlertCell)?.render(with: item)
})
}
}
这是完美的,但是,这些单元格中的每一个都有不同的设计,但实际上接受非常相似的属性(其他单元格也是如此)-因此我正在考虑创建一个简单的 FeedItemModel
并在呈现我的提要之前映射这些属性。这将确保在我呈现提要项目的任何地方,我总是处理相同的属性。
考虑到这一点,我尝试创建类似的东西:-
extension CollectionViewDataSource where Model == FeedItemModel {
static func make(reuseIdentifier: String = "FEED_ARTICLE_CELL") -> CollectionViewDataSource {
return CollectionViewDataSource(reuseIdentifier: reuseIdentifier, cellConfigurator: { item, cell in
switch item.type {
case .news: (cell as? FeedArticleCell)?.render(with: item)
case .alert: (cell as? FeedAlertCell)?.render(with: item)
}
})
}
}
然而,如果 item.type
为 .alert
,则 reuseIdentifier
字段不再正确,这会下降。
如何重构此模式以允许我对同一模型使用不同的单元格类型?或者我应该放弃这种方法并坚持为每种细胞类型使用不同的模型,而不管输入属性是否相同?
如何为单元格添加第二个泛型类型
final class CollectionViewDataSource<Model, CellType : UICollectionViewCell>: NSObject, UICollectionViewDataSource {
typealias CellConfigurator = (Model, CellType) -> Void
var models: [Model] = []
private let reuseIdentifier: String
private let cellConfigurator: CellConfigurator
init(reuseIdentifier: String, cellConfigurator: @escaping CellConfigurator) {
self.reuseIdentifier = reuseIdentifier
self.cellConfigurator = cellConfigurator
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return models.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let model = models[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CellType
cellConfigurator(model, cell)
return cell
}
}
您可以在 FeedItemModelType
中添加关联标识符
var reuseIdentifier: String {
switch self {
case .news: return "NEW_CELL"
case .alert: return "ALERT_CELL"
}
}
你的工厂方法看起来像这样
extension CollectionViewDataSource where Model == FeedItemModel {
static func make() -> CollectionViewDataSource {
return CollectionViewDataSource(reuseIdentifier: item.type.reuseIdentifier, cellConfigurator: { item, cell in
switch item.type {
case .news: (cell as? FeedArticleCell)?.render(with: item)
case .alert: (cell as? FeedAlertCell)?.render(with: item)
}
})
}
}
您可以创建一个协议,例如
protocol FeedRenderable {
var reuseIdentifier: String { get }
}
然后确保Model
类型符合FeedRenderable
.
然后您可以将 CollectionViewDataSource
重构为
final class CollectionViewDataSource<Model>: NSObject, UICollectionViewDataSource where Model: FeedRenderable {
typealias CellConfigurator = (Model, UICollectionViewCell) -> Void
var models: [Model] = []
private let cellConfigurator: CellConfigurator
init(_ cellConfigurator: @escaping CellConfigurator) {
self.cellConfigurator = cellConfigurator
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return models.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let model = models[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: model.reuseIdentifier, for: indexPath)
cellConfigurator(model, cell)
return cell
}
}
注意以下变化
final class CollectionViewDataSource<Model>: NSObject, UICollectionViewDataSource where Model: FeedRenderable {
....
init(_ cellConfigurator: @escaping CellConfigurator)
....
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: model.reuseIdentifier, for: indexPath)
....
然后您可以确保传入的任何模型都根据 item.type
属性
设置其 reuseIdentifier
extension GenericFeedItem: FeedRenderable {
var reuseIdentifier: String {
switch type {
case .news: return "FEED_ARTICLE_CELL"
case .alert: return "FEED_ALERT_CELL"
}
}
}
您的分机将变为
extension CollectionViewDataSource where Model == GenericFeedItem {
static func make() -> CollectionViewDataSource {
return CollectionViewDataSource() { item, cell in
(cell as? FeedArticleCell)?.render(with: item)
(cell as? FeedAlertCell)?.render(with: item)
}
}
}
基于此 article 我为 UICollectionView
创建了一个可重用的数据源,如下所示:-
final class CollectionViewDataSource<Model>: NSObject, UICollectionViewDataSource {
typealias CellConfigurator = (Model, UICollectionViewCell) -> Void
var models: [Model] = []
private let reuseIdentifier: String
private let cellConfigurator: CellConfigurator
init(reuseIdentifier: String, cellConfigurator: @escaping CellConfigurator) {
self.reuseIdentifier = reuseIdentifier
self.cellConfigurator = cellConfigurator
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return models.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let model = models[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
cellConfigurator(model, cell)
return cell
}
}
然后我扩展了这个 class 所以我可以提供 'cell specific' 基于模型类型的设置
extension CollectionViewDataSource where Model == HomeFeedItem {
static func make(reuseIdentifier: String = "FEED_ARTICLE_CELL") -> CollectionViewDataSource {
return CollectionViewDataSource(reuseIdentifier: reuseIdentifier, cellConfigurator: { item, cell in
(cell as? FeedArticleCell)?.render(with: item)
})
}
}
extension CollectionViewDataSource where Model == HomeFeedAlertItem {
static func make(reuseIdentifier: String = "FEED_ALERT_CELL") -> CollectionViewDataSource {
return CollectionViewDataSource(reuseIdentifier: reuseIdentifier, cellConfigurator: { item, cell in
(cell as? FeedAlertCell)?.render(with: item)
})
}
}
这是完美的,但是,这些单元格中的每一个都有不同的设计,但实际上接受非常相似的属性(其他单元格也是如此)-因此我正在考虑创建一个简单的 FeedItemModel
并在呈现我的提要之前映射这些属性。这将确保在我呈现提要项目的任何地方,我总是处理相同的属性。
考虑到这一点,我尝试创建类似的东西:-
extension CollectionViewDataSource where Model == FeedItemModel {
static func make(reuseIdentifier: String = "FEED_ARTICLE_CELL") -> CollectionViewDataSource {
return CollectionViewDataSource(reuseIdentifier: reuseIdentifier, cellConfigurator: { item, cell in
switch item.type {
case .news: (cell as? FeedArticleCell)?.render(with: item)
case .alert: (cell as? FeedAlertCell)?.render(with: item)
}
})
}
}
然而,如果 item.type
为 .alert
,则 reuseIdentifier
字段不再正确,这会下降。
如何重构此模式以允许我对同一模型使用不同的单元格类型?或者我应该放弃这种方法并坚持为每种细胞类型使用不同的模型,而不管输入属性是否相同?
如何为单元格添加第二个泛型类型
final class CollectionViewDataSource<Model, CellType : UICollectionViewCell>: NSObject, UICollectionViewDataSource {
typealias CellConfigurator = (Model, CellType) -> Void
var models: [Model] = []
private let reuseIdentifier: String
private let cellConfigurator: CellConfigurator
init(reuseIdentifier: String, cellConfigurator: @escaping CellConfigurator) {
self.reuseIdentifier = reuseIdentifier
self.cellConfigurator = cellConfigurator
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return models.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let model = models[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CellType
cellConfigurator(model, cell)
return cell
}
}
您可以在 FeedItemModelType
var reuseIdentifier: String {
switch self {
case .news: return "NEW_CELL"
case .alert: return "ALERT_CELL"
}
}
你的工厂方法看起来像这样
extension CollectionViewDataSource where Model == FeedItemModel {
static func make() -> CollectionViewDataSource {
return CollectionViewDataSource(reuseIdentifier: item.type.reuseIdentifier, cellConfigurator: { item, cell in
switch item.type {
case .news: (cell as? FeedArticleCell)?.render(with: item)
case .alert: (cell as? FeedAlertCell)?.render(with: item)
}
})
}
}
您可以创建一个协议,例如
protocol FeedRenderable {
var reuseIdentifier: String { get }
}
然后确保Model
类型符合FeedRenderable
.
然后您可以将 CollectionViewDataSource
重构为
final class CollectionViewDataSource<Model>: NSObject, UICollectionViewDataSource where Model: FeedRenderable {
typealias CellConfigurator = (Model, UICollectionViewCell) -> Void
var models: [Model] = []
private let cellConfigurator: CellConfigurator
init(_ cellConfigurator: @escaping CellConfigurator) {
self.cellConfigurator = cellConfigurator
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return models.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let model = models[indexPath.item]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: model.reuseIdentifier, for: indexPath)
cellConfigurator(model, cell)
return cell
}
}
注意以下变化
final class CollectionViewDataSource<Model>: NSObject, UICollectionViewDataSource where Model: FeedRenderable {
....
init(_ cellConfigurator: @escaping CellConfigurator)
....
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: model.reuseIdentifier, for: indexPath)
....
然后您可以确保传入的任何模型都根据 item.type
属性
reuseIdentifier
extension GenericFeedItem: FeedRenderable {
var reuseIdentifier: String {
switch type {
case .news: return "FEED_ARTICLE_CELL"
case .alert: return "FEED_ALERT_CELL"
}
}
}
您的分机将变为
extension CollectionViewDataSource where Model == GenericFeedItem {
static func make() -> CollectionViewDataSource {
return CollectionViewDataSource() { item, cell in
(cell as? FeedArticleCell)?.render(with: item)
(cell as? FeedAlertCell)?.render(with: item)
}
}
}