Swift中具有不同单元格类型的可重用数据源

时间:2020-01-16 08:12:38

标签: ios swift uicollectionview uicollectionviewcell uicollectionviewdelegate

基于此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并在呈现我的Feed之前先映射这些属性。这样可以确保在渲染提要项的任何地方,我始终都处理相同的属性。

考虑到这一点,我试图创建类似:-

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)
      }
    })
  }
}

但是,由于reuseIdentifieritem.type.alert字段不再正确,因此该值下降。

如何重构此模式以允许我在同一模型中使用不同的单元格类型?还是我应该放弃这种方法而对每种单元格类型使用不同的模型,而不管输入属性是否相同?

3 个答案:

答案 0 :(得分:1)

您可以将关联标识符添加到您的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)
      }
    })
  }
}

答案 1 :(得分: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)
....

然后您可以确保根据reuseIdentifier属性将传入的任何模型设置为其item.type

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 :(得分:0)

如何为单元格添加第二个通用类型

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
    }
}
相关问题