iago849
iago849

Reputation: 1854

RxSwift: how to subscribe to change of an array element member change?

Let's say we have a class Dog

class Dog {
    let age = BehaviorRelay<Int>(value: 1)
}

and somewhere we have an array property with all the dogs

let dogs = BehaviorRelay<[Dog]>(value: [..., ...])

now I want to create UI which needs array of dogs listed in UITableView, and it wants to be updated (call reloadDate under the hood) when age of each dog changes?

So when I simply subscribe like that:

dogs.subscribe(onNext: {
    print("\($0)")
})

the subscription will get fired when new dog comes to array, or some leaves array, but not when dogs mature, i.e.:

dogs.value[1].age.accept(2)

I know about flatMap and flatMapLatest, but they seems to expects Dogs to be a plain type, not array. I'm new to RxSwift, any help will be appreciated!

Upvotes: 3

Views: 999

Answers (1)

Daniel T.
Daniel T.

Reputation: 33979

So when you have a setup as described, then the table view itself would be bound to the dogs observable... dogs.bind(to: tableView.rx.items... Then inside the closure, you would have the cell bind to the age observable to display the current age. With all this in place, if you update the age of a particular dog, the cell will automatically update without having to reload the entire tableView. The only time the tableView will reload is when the new dogs are added, or dogs are removed from the array. You can limit even that behavior to just the dog(s) that are added/removed by including a custom data source (or by using the RxDataSources library) to load/unload just the dog(s) that were added/removed.

For reference, here is some example code:

class Dog {
    let age = BehaviorRelay<Int>(value: 1)
}

class DogController: UIViewController {
    @IBOutlet var tableView: UITableView!
    let dogs = BehaviorRelay<[Dog]>(value: [])
    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        dogs
            .bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: DogCell.self)) { _, dog, cell in
                cell.configure(dog: dog)
            }
            .disposed(by: disposeBag)
    }
}

class DogCell: UITableViewCell {
    @IBOutlet var ageLabel: UILabel!
    var disposeBag = DisposeBag()
    override func prepareForReuse() {
        super.prepareForReuse()
        disposeBag = DisposeBag()
    }
    func configure(dog: Dog) {
        dog.age
            .map { "\($0) years old" }
            .bind(to: ageLabel.rx.text)
            .disposed(by: disposeBag)
    }
}

With the above, the cell that is displaying dog[1] will be listening to the dog's age and if you call dogs.value[1].age.accept(2), that cell will notice the change and update itself.

Upvotes: 1

Related Questions