4

How to achieve self sizing collectionViewCells using RxDataSource?

I've tried setting

flowLayout.estimatedItemSize = CGSize(width: 187, height: 102)

But then app crashes when dataSourceObservable changes.

I've tried setting cell size inside

dataSource.configureCell = { [weak self] (dataSource, collectionView, indexPath, _) in 

Which is not a good idea, because cells overlap, and it is because I am not using

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize

Now, is possible to layout cell sizes properly using only observables? That is not to call something like

dataSourceVar.value[indexPath].cellSize

Inside collectionView sizeForItemAt?

Xernox
  • 1,706
  • 1
  • 23
  • 37

2 Answers2

1

Faced a similar problem, solved in this way

// swiftlint:disable line_length type_name
final class RxCollectionViewSectionedReloadDataSourceAndDelegate<Section: SectionModelType>: RxCollectionViewSectionedReloadDataSource<Section>, UICollectionViewDelegateFlowLayout {
    typealias CellSize = (CollectionViewSectionedDataSource<Section>, UICollectionView, UICollectionViewLayout, IndexPath, Item) -> CGSize
    typealias SizeViewInSection = (CollectionViewSectionedDataSource<Section>, UICollectionView, UICollectionViewLayout, Int, Section) -> CGSize
    
    private var cellSize: CellSize
    private var headerSectionViewSize: SizeViewInSection?
    private var footerSectionViewSize: SizeViewInSection?
    
    init(
        configureCell: @escaping ConfigureCell,
        configureSupplementaryView: ConfigureSupplementaryView? = nil,
        moveItem: @escaping MoveItem = { _, _, _ in () },
        canMoveItemAtIndexPath: @escaping CanMoveItemAtIndexPath = { _, _ in false },
        cellSize: @escaping CellSize,
        headerSectionViewSize: SizeViewInSection? = nil,
        footerSectionViewSize: SizeViewInSection? = nil
    ) {
        self.cellSize = cellSize
        self.headerSectionViewSize = headerSectionViewSize
        self.footerSectionViewSize = footerSectionViewSize
        super.init(
            configureCell: configureCell,
            configureSupplementaryView: configureSupplementaryView,
            moveItem: moveItem,
            canMoveItemAtIndexPath: canMoveItemAtIndexPath
        )
    }
    
    override func collectionView(
        _ collectionView: UICollectionView,
        observedEvent: Event<RxCollectionViewSectionedReloadDataSource<Section>.Element>
    ) {
        collectionView.delegate = self
        super.collectionView(collectionView, observedEvent: observedEvent)
    }
    
    func collectionView(
        _ collectionView: UICollectionView,
        layout collectionViewLayout: UICollectionViewLayout,
        sizeForItemAt indexPath: IndexPath
    ) -> CGSize {
        cellSize(self, collectionView, collectionViewLayout, indexPath, self[indexPath])
    }
    
    func collectionView(
        _ collectionView: UICollectionView,
        layout collectionViewLayout: UICollectionViewLayout,
        referenceSizeForHeaderInSection section: Int
    ) -> CGSize {
        headerSectionViewSize?(self, collectionView, collectionViewLayout, section, sectionModels[section]) ?? .zero
    }
    
    func collectionView(
        _ collectionView: UICollectionView,
        layout collectionViewLayout: UICollectionViewLayout,
        referenceSizeForFooterInSection section: Int
    ) -> CGSize {
        footerSectionViewSize?(self, collectionView, collectionViewLayout, section, sectionModels[section]) ?? .zero
    }
}
// swiftlint:enable line_length type_name
Flair
  • 2,609
  • 1
  • 29
  • 41
Muzzle
  • 61
  • 4
0

Add collection view to Storyboard. Import RxDataSources as a dependency.

import UIKit
import RxSwift
import RxCocoa
import RxDataSources

class ViewController: UIViewController {

  private let disposeBag = DisposeBag()
  
  @IBOutlet weak var collectionView: UICollectionView!
  @IBOutlet weak var collectionLayout: UICollectionViewFlowLayout! {
    didSet {
      collectionLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
    }
  }
  
  private let section = BehaviorRelay(
    value: Section(items: [
      Item(title: "Lorem ipsum dolor sit amet, consectetur"),
      Item(title: "adipiscing elit, sed do eiusmod tempor"),
      Item(title: "incididunt ut labore et dolore magna aliqua"),
      Item(title: "Ut enim ad minim veniam"),
      Item(title: "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."),
      Item(title: "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
      ]
    )
  )

  private lazy var collectionDatasource = {
    return RxCollectionViewSectionedReloadDataSource<Section>(
      configureCell: { (dataSource, collectionView, indexPath, item) -> UICollectionViewCell in
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionCell
        cell.titleLabel.text = item.title
        cell.layer.borderWidth = 0.5
        cell.layer.borderColor = UIColor.lightGray.cgColor
        cell.maxWidth = collectionView.bounds.width - 16
        return cell
    })
  }()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    initCollection()
  }
  
  private func initCollection() {
    section
      .asObservable()
      .map({ return [$0] })
      .bind(to: collectionView.rx.items(dataSource: collectionDatasource))
      .disposed(by: disposeBag)
  }
}

Data Model

  import Foundation
  
  struct Item {
    let title: String
  }

Create Section class subclassing SectionModelType

import RxDataSources

  struct Section {
    var items: [Item]
  }
  
  extension Section: SectionModelType {
    
    init(
      original: Section,
      items: [Item]) {
      
      self = original
      self.items = items
    }
  }

Collection View Cell class

import UIKit

class CollectionCell: UICollectionViewCell {
  @IBOutlet weak var titleLabel: UILabel!
  
  // Note: must be strong
  @IBOutlet private var maxWidthConstraint: NSLayoutConstraint! {
      didSet {
          maxWidthConstraint.isActive = false
      }
  }
  
  var maxWidth: CGFloat? = nil {
      didSet {
          guard let maxWidth = maxWidth else {
              return
          }
          maxWidthConstraint.isActive = true
          maxWidthConstraint.constant = maxWidth
      }
  }
  
  override func awakeFromNib() {
      super.awakeFromNib()
      
      contentView.translatesAutoresizingMaskIntoConstraints = false
      
      NSLayoutConstraint.activate([
          contentView.leftAnchor.constraint(equalTo: leftAnchor),
          contentView.rightAnchor.constraint(equalTo: rightAnchor),
          contentView.topAnchor.constraint(equalTo: topAnchor),
          contentView.bottomAnchor.constraint(equalTo: bottomAnchor)
          ])
  }
}
TylerJames
  • 941
  • 8
  • 27