I'm using RxSwift
with my UIViewController
that contains a UICollectionView
. I have tried to add a header to my collection view but this is never called:
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath)
To display this section header, you'll use UICollectionReusableView . This class is like UICollectionViewCell , except it's usually used to display headers and footers. Like cells, UICollectionView places them in a reuse queue rather than deleting them when they scroll out of the visible bounds.
An object that manages an ordered collection of data items and presents them using customizable layouts.
Use a cell registration to register cells with your collection view and configure each cell for display. You create a cell registration with your cell type and data item type as the registration's generic parameters, passing in a registration handler to configure the cell.
After a long time figuring it out I have a complete answer. The key part depends on the RxDataSources dataSource.configureCell and dataSource.supplementaryViewFactory.
You will need to setup by registering your nibs normally. To make the collection view show it's header you will need to define a header size. Using the collection flow layout allows you preserve any bindings you might have inside cells for clickhandlers and not override the rx delegate proxy RxCocoa is using internally.
In this example I have one viewModel for the whole screen which holds an RxVariable with an array of itemViewModels for each item in the collection view.
UICollectionView with headers using RxSwift, RxDataSources, and RxCocoa:
var disposeBag: DisposeBag? = DisposeBag()
@IBOutlet weak var collectionView: UICollectionView!
let dataSource = RxCollectionViewSectionedReloadDataSource<SectionModel<String, ItemViewModel>>()
override func viewDidLoad() {
super.viewDidLoad()
registerHeaderCell()
registerCollectionViewCell()
setupLayout()
bindViewModelToCollectionView()
}
private func registerHeaderCell() {
let nib = UINib(nibName: "HeaderCell", bundle: nil)
collectionView.register(nib, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderReuseId")
}
private func registerCollectionViewCell() {
let nib = UINib(nibName:"CollectionViewCell", bundle: nil)
collectionView.register(nib, forCellWithReuseIdentifier: "CellReuseId")
}
private func setupLayout(){
let layout = UICollectionViewFlowLayout()
layout.headerReferenceSize = CGSize(width: UIScreen.main.bounds.width, height: 100)
collectionView.setCollectionViewLayout(layout, animated: false)
}
private func bindViewModelToCollectionView(){
dataSource.supplementaryViewFactory = {(dataSource, collectionView, kind, indexPath) -> UICollectionReusableView in
let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "HeaderReuseId", for: indexPath) as! HeaderCell
header.setup()
return header
}
dataSource.configureCell = {(datasource, collectionView, indexPath, itemViewModel) -> UICollectionViewCell in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CellReuseId", for: indexPath) as! CollectionViewCell
cell.setup()
return cell
}
viewModel.itemViewModels.asObservable().map {
(itemViewModels) -> [SectionModel<String, ItemViewModel>] in
//my particular app has one big section but you can use this for mutiple sections like Sergiy's answer
return [SectionModel(model: "", items: itemViewModels)]
}.bind(to: collectionView.rx.items(dataSource: dataSource))
.addDisposableTo(disposeBag!)
}
deinit {
disposeBag = nil
}
I had same problem. Solve it moving to RxCollectionViewSectionedReloadDataSource
This type of datasource has headers/footers. Standard one RxCollectionViewReactiveArrayDataSource doesn't have headers/footers.
Even in RxCocoa UICollectionView+Rx extension gives RxCollectionViewSectionedReloadDataSource as example.
Example:
let dataSource = RxCollectionViewSectionedReloadDataSource<SectionModel<String, Double>>()
let items = Observable.just([
SectionModel(model: "First section", items: [
1.0,
2.0,
3.0
]),
SectionModel(model: "Second section", items: [
1.0,
2.0,
3.0
]),
SectionModel(model: "Third section", items: [
1.0,
2.0,
3.0
])
])
dataSource.configureCell = { (dataSource, cv, indexPath, element) in
let cell = cv.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! NumberCell
cell.value?.text = "\(element) @ row \(indexPath.row)"
return cell
}
items
.bind(to: collectionView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With