Swift 中具有/不同单元格类型的可重用数据源
Posted
技术标签:
【中文标题】Swift 中具有/不同单元格类型的可重用数据源【英文标题】:Reusable data sources in Swift w/ different cell types 【发布时间】:2020-01-16 08:12:38 【问题描述】:基于此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
然后我扩展了这个类,因此我可以根据模型的类型提供“特定于单元格”的设置
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
字段不再正确,因此这会下降。
如何重构此模式以允许我在同一模型中使用不同的细胞类型?或者我应该放弃这种方法并为每种细胞类型坚持不同的模型,而不管输入属性是否相同?
【问题讨论】:
您的模型是否等于 .alert 的 FeedItemModel? 是的,我FeedItemModel
是一个通用模型,我正在将每种模型类型的相关值映射到该模型,因此提要项目只需要使用FeedItemModel
,而不管它们的类型
好的,什么意思是“但是由于reuseIdentifier 字段不再正确,所以这会下降”,你这里有崩溃吗?还是错误的标识符?
标识符错误,因为每个单元格都注册了自己的标识符,所以重复使用 FeedArticleCell
和 FeedAlertCell
使用值 make(reuseIdentifier: String = "FEED_ARTICLE_CELL")
- 因为它们是我想设置的不同单元格reuseIdentifier
基于每个模型具有的item.type
。
它不会崩溃,而只是呈现错误的单元格类型,因为我认为它使用reuseIdentifier
来准备单元格。
【参考方案1】:
你可以创建一个协议比如
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)
【讨论】:
【参考方案2】:您可以将关联标识符添加到您的 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)
)
【讨论】:
这会产生Use of unresolved identifier 'item'
,因为item
不存在于make() -> CollectionViewDataSource
的范围内【参考方案3】:
如何为单元格添加第二个泛型类型
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
【讨论】:
这是否需要我为每种类型的提要项使用数据源? 你可以像你一样使用它,但是你会得到一个静态单元格类型和静态模型类型以上是关于Swift 中具有/不同单元格类型的可重用数据源的主要内容,如果未能解决你的问题,请参考以下文章
创建类、结构或新的 swift 文件以创建具有特定样式的可重用文本框?