【问题标题】:Reusable data sources in Swift w/ different cell typesSwift 中具有/不同单元格类型的可重用数据源
【发布时间】: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 来准备单元格。

标签: ios swift uicollectionview uicollectionviewcell uicollectionviewdelegate


【解决方案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
        }
    }
    

    【讨论】:

    • 这是否需要我为每种类型的提要项使用数据源?
    • 你可以像你一样使用它,但是你会得到一个静态单元格类型和静态模型类型
    猜你喜欢
    • 2013-06-02
    • 2019-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-28
    • 1970-01-01
    • 2023-03-03
    • 2015-12-19
    相关资源
    最近更新 更多