Problem: CollectionView header and footer are not shown before results are retrieved from network service. They are shown after items are retrieved from service.
Result I want:: Show CollectionView header and footer before items are retrieved from api call
What i've tried: Used different datasource with header and footer on initial viewDidLoad and set that datasource to nil when results loaded. Didn't work
My (not so elegant) solution: Add headerView and footerView subviews on viewDidLoad and replace them with collectionView when results come from service.
itemsLoading.drive(onNext: { loading in
if loading {
view.addSubview(headerView)
view.addSubview(footerView)
} else {
headerView.removeFromSuperview()
footerView.removeFromSuperView()
view.addSubview(collectionView)
Is there more elegant solution to this problem ?
My Code: Left only parts related to Rxswift/RxDataSources
struct Input {
let id: Driver<String>
}
struct Output {
let items: Driver<[Item]>
}
class ViewModel {
func transform(input: Input) -> Output {
let items = networkService.getItems(id: input.id) // async operation
return Output(items: items)
}
}
class ViewController: UIViewController {
private let viewModel = ViewModel()
private let disposeBag = DisposeBag()
private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
}
private func bindViewModel() {
let dataSource = ViewController.dataSource()
let id = .just(id)
let input = Input(id: id)
let output = viewModel.transform(input: input)
output.items.map { items in
items.map { item in
SectionModel(model: "first Section", items: [item])
}
}
.drive(collectionView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
extension ViewController {
typealias DataSource = RxCollectionViewSectionedReloadDataSource
static func dataSource() -> DataSource<SectionModel<String, Item>> {
.init(
configureCell: { _, collectionView, indexPath, _ in
let cell = collectionView.dequeueReusableCell(ofType: UICollectionViewCell.self, for: indexPath)
return cell
},
configureSupplementaryView: { _, collectionView, kind, indexPath in
switch kind {
case UICollectionView.elementKindSectionHeader:
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, kindType: SectionHeader.self, for: indexPath)
return header
case UICollectionView.elementKindSectionFooter:
let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, kindType: SectionFooter.self, for: indexPath)
return footer
default:
assert(false, "Unexpected element kind")
}
})
}
}
The simple solution, and I feel more elegant, is to use startWith
to emit an array of SectionModel
s that each have 0 items, but have the model
contain the right value.