简体   繁体   中英

RxSwift - Can't bind variable of custom UICollectionViewCell

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?

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM