简体   繁体   English

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

[英]Reusable data sources in Swift w/ different cell types

Based on this article I have created a reusable data source for UICollectionView as follows :-基于这篇文章,我为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
  }
}

I have then extended this class so I can provide 'cell specific' setup based on the type of model然后我扩展了这个类,以便我可以根据模型类型提供“特定于单元格”的设置

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

This is working perfect, however, each of these cells has a different design but does in fact accept very similar properties (as do the other cells) - because of this I was thinking of creating a simple FeedItemModel and mapping these properties prior to rendering my feed.这是完美的,但是,这些单元格中的每一个都有不同的设计,但实际上接受非常相似的属性(与其他单元格一样) - 因此我正在考虑创建一个简单的FeedItemModel并在渲染我的之前映射这些属性喂养。 This would ensure anywhere I rendered a feed item, I was always dealing with the same properties.这将确保在我渲染提要项目的任何地方,我总是处理相同的属性。

With that in mind I tried to create something like :-考虑到这一点,我尝试创建类似的东西:-

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

This however falls down as the reuseIdentifier field is no longer correct if item.type is .alert .然而,这倒下的reuseIdentifier场不再是正确的,如果item.type.alert

How can I refactor this pattern to allow me to use different cells types with the same model?如何重构此模式以允许我使用具有相同模型的不同单元格类型? Or should I abandon this approach and stick to a different model for each cell type regardless of the input properties being the same?或者我应该放弃这种方法并坚持为每个单元格类型使用不同的模型,而不管输入属性是否相同?

You can add a associate identifier to your FeedItemModelType您可以向FeedItemModelType添加关联标识符

var reuseIdentifier: String {
    switch self {
        case .news: return "NEW_CELL"
        case .alert: return "ALERT_CELL"
   }
}

And your factory method would look like this你的工厂方法看起来像这样

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

You can create a protocol such as您可以创建一个协议,例如

protocol FeedRenderable {
  var reuseIdentifier: String { get }
}

Then ensure the Model type conforms to FeedRenderable .然后确保Model类型符合FeedRenderable

You can then refactor your CollectionViewDataSource to然后,您可以将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
  }
}

Notice the following changes注意以下变化

final class CollectionViewDataSource<Model>: NSObject, UICollectionViewDataSource where Model: FeedRenderable {
....
init(_ cellConfigurator: @escaping CellConfigurator) 
....
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: model.reuseIdentifier, for: indexPath)
....

You can then ensure whatever model is passed in sets its reuseIdentifier based on the item.type property然后,您可以确保任何模型在其套通过reuseIdentifier基础上的item.type财产

extension GenericFeedItem: FeedRenderable {
  var reuseIdentifier: String {
    switch type {
    case .news: return "FEED_ARTICLE_CELL"
    case .alert: return "FEED_ALERT_CELL"
    }
  }
}

Your extension then becomes你的分机然后变成

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

How about adding a second generic type for the cell如何为单元格添加第二个泛型类型

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM