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 字段不再正确,所以这会下降”,你这里有崩溃吗?还是错误的标识符? 标识符错误,因为每个单元格都注册了自己的标识符,所以重复使用 FeedArticleCellFeedAlertCell 使用值 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() -&gt; 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 中具有/不同单元格类型的可重用数据源的主要内容,如果未能解决你的问题,请参考以下文章

UITableView 单元格中的可重用单元格存在问题

与可重用表格单元格不同的转场

创建类、结构或新的 swift 文件以创建具有特定样式的可重用文本框?

Swift 3 - 使用多个自定义单元重用单元的问题

Swift 3 - 已展开第一个单元格的可展开表格视图单元格

Swift - uitableview 重用单元格时用户输入混淆?