Многоразовые источники данных в Swift с различными типами ячеек - PullRequest
1 голос
/ 16 января 2020

На основании этой статьи я создал многократно используемый источник данных для 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
  }
}

Затем я расширил этот класс, чтобы я мог предоставить 'спецификацию ячейки c 'установка основана на типе модели

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

Это, однако, падает поскольку поле reuseIdentifier больше не является правильным, если item.type равно .alert.

Как я могу реорганизовать этот шаблон, чтобы позволить мне использовать разные типы ячеек с одной и той же моделью? Или я должен отказаться от этого подхода и придерживаться другой модели для каждого типа ячеек, независимо от того, какие свойства ввода одинаковы?

Ответы [ 3 ]

1 голос
/ 16 января 2020

Вы можете добавить ассоциированный идентификатор к вашему 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 голос
/ 16 января 2020

Вы можете создать протокол, такой как

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)
    }
  }
}
0 голосов
/ 16 января 2020

Как насчет добавления второго универсального c типа для ячейки

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
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...