Reputation: 1885
I have a custom UICollectionViewCell
with a UILabel
inside it.
Is also has the property hour
, which is String?
for the sake of the example.
I made a private function named bind
to configure my bindings. Here is my class:
class HourCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var hourLabel UILabel!
let hour Variable<String?> = Variable<String?>("")
let disposeBag: DisposeBag = DisposeBag()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
bind()
}
private func bind() {
hour.asObservable().bind(to: hourLabel.rx.text).disposed(by: disposeBag)
hour.asObservable().subscribe(onNext: {debugPrint("New hour: \($0 ?? "--")")}).disposed(by: disposeBag)
}
}
Calling bind
inside the required init might be a problem. Sometimes the hourLabel
is still not initialized. Also, calling this function inside init(frame: CGRect)
never gets the bind
function to be triggered, which is awkward. I thought this function was always called.
Even though this is a simple binding, I can't achieve it correctly.
From inside my UICollectionViewController
, I have this function which fills the hour
property from my custom cell:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HourCollectionViewCell", for: indexPath) as! HourCollectionViewCell
var text: String = "\(indexPath.row)"
// This does not work because `bind` method
// is never called.
cell.hour.value = text
return cell
}
UPDATE:
I've seen that init:coder
is called when I'm initializing the View from a Storyboard, which is the case.
A workaround that I've seen here is to call bind
from inside layoutSubviews
. This way everything worked. Is this the better approach?
Upvotes: 1
Views: 1202
Reputation: 1118
Your cell is reusable, so you should clear the dispose bag.
class HourCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var hourLabel UILabel!
let hour = PublishRelay<String?>()
private(set) var disposeBag = DisposeBag()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func bind() {
hour.asDriver(onErrorJustReturn: "").debug().drive( hourLabel.rx.text).disposed(by: disposeBag)
}
override prepareForReuse() {
super.prepareForReuse()
disposeBag = DisposeBag()
}
}
I used a PublishRelay
as Variable
is deprecated and made it a Driver
to make sure we're on the mainThread.
You can use debug()
to print the events in the console.
Because on reuse we clear the disposeBag, we can call bind from within UICollectionViewController:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HourCollectionViewCell", for: indexPath) as! HourCollectionViewCell
var text: String = "\(indexPath.row)"
cell.bind()
cell.hour.accept(text)
return cell
}
I encourage you to have a look at RxDataSources
as it's probably better for the UICollectionViewController to provide a model for each cell.
Upvotes: 3