1

I am using MVVM pattern with RxSwift, RxCocoa, RxDataSources.

enter image description here

I have successfully populated the UITableView with array of PaletteViewModel present in ListViewModel by using RxDataSource but it's one way binding.

I want to achieve what I have shown in the picture i.e. I want to bind the UITextField from UITableViewCell to the Observable which is present at some index in the array in ListViewModel

I want to do 2 way binding with the UITextField and answer property of the PaletteViewModel. If the user changes the text in the textField it should change the value in the answer property present at particular index and vice versa.

How Can I achieve something complex like this using MVVM pattern using ReactiveX frameworks?

What if the UITableViewCell at some IndexPath is removed from the memory as it's not visible and the observable's value is changed will it result in crash as the UITextField at that IndexPath will return nil?

Anirudha Mahale
  • 2,526
  • 3
  • 37
  • 57

1 Answers1

3

A UITextField is an input element. You don't need a two way binding to it because you shouldn't be dynamically changing it. The most you should do is initialize it and you don't need a binding for that.

You don't mention what the final output will be for this input so the answer may be different than the below. This particular solution assumes that you need to push all the answers as a group to a server or database. Maybe when a button is tapped.

There is a lot of code below, but it compiles as it stands (with the proper imports.) You can subscribe to ListViewModel.answers to see all the answers collected together.

class ViewController: UIViewController {

    @IBOutlet weak var myTableView: UITableView!
    let bag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        let answersSubject = PublishSubject<(PaletteID, String)>()
        let viewModel = ListViewModel(answersIn: answersSubject.asObservable())

        viewModel.paletteViewModels
            .bind(to: myTableView.rx.items(cellIdentifier: "Cell", cellType: MyCell.self)) { index, element, cell in
                cell.answerTextField.text = element.initialAnswer
                cell.answerTextField.rx.text.orEmpty
                    .map { (element.id, $0) }
                    .bind(to: answersSubject)
                    .disposed(by: cell.bag)
            }
            .disposed(by: bag)
    }
}

class MyCell: UITableViewCell {
    @IBOutlet weak var answerTextField: UITextField!
    let bag = DisposeBag()
}

struct ListViewModel {
    let paletteViewModels: Observable<[PaletteViewModel]>
    let answers: Observable<[PaletteID: String]>

    init(answersIn: Observable<(PaletteID, String)>) {
        paletteViewModels = Observable.just([])
        answers = answersIn
            .scan(into: [PaletteID: String]()) { current, new in
                current[new.0] = new.1
            }
    }
}

struct PaletteViewModel {
    let id: PaletteID
    let initialAnswer: String
}

struct PaletteID: RawRepresentable, Hashable {
    let rawValue: String
}
Daniel T.
  • 32,821
  • 6
  • 50
  • 72