SimpuMind
SimpuMind

Reputation: 469

RxSwift: Observing UITextFields from TableViewCell inside a child view controller

I have a tableView and a childController in a parent viewController, the tableView in the ParentViewController can have from 1 - 4 cells, each cell contains a UITextField.

The ChildController also have a TableView, that list results(autocomplete) based on what is inputted in any of the TextField in the ParentViewController tableView cell.

I want the childController to always listen to any of the UITextField and show the result on the tablView. This is what I have currently

private var query = Variable<String>("")
var queryDriver: Driver<String> {
    return query.asDriver()
}

TableView

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    guard let cell = tableView
        .dequeueReusableCell(
            withIdentifier: "StopCell", for: indexPath) as? StopCell else {
                fatalError("Cannot dequeue StopCell")
    }
    cell.delegate = self

    cell.locationTextField.rx.text.map {$0 ?? ""}
        .bind(to: query)
        .disposed(by: disposeBag)

    cell.locationTextField.rx.controlEvent(.editingDidEnd)
        .asDriver(onErrorJustReturn: ())
        .drive(onNext: { [unowned self] in
            cell.locationTextField.resignFirstResponder()
        })
        .disposed(by: disposeBag)
    return cell
}

Add child controller

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .white
    let noteVC = NoteVc()
    addChildController(viewController: noteVC)
}

NoteVC

class NoteVc: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .white
    view.addSubview(tableView)
    viewModel = SearchLocationViewModel(query: <#T##SharedSequence<DriverSharingStrategy, String>#>)
}

ViewModel

class SearchLocationViewModel {

let disposeBag = DisposeBag()
// MARK: - Properties

var querying: Driver<Bool> { return _querying.asDriver() }
var locations: Driver<[Location]> { return _locations.asDriver() }

// MARK: -

var hasLocations: Bool { return numberOfLocations > 0 }
var numberOfLocations: Int { return _locations.value.count }

// MARK: -

private let _querying = BehaviorRelay<Bool>(value: false)
private let _locations = BehaviorRelay<[Location]>(value: [])

// MARK: -

private let disposeBag = DisposeBag()

// MARK: - Initializtion

init(query: Driver<String>) {
    Behave.shared.queryDriver
        .throttle(0.5)
        .distinctUntilChanged()
        .drive(onNext: { [weak self] (addressString) in
            self?.geocode(addressString: addressString)
        })
        .disposed(by: disposeBag)
}

Like it is implemented in the Uber app, users can add up to three destinations, the yellow rectangle box in the image below is my ChildViewController

enter image description here

Upvotes: 1

Views: 1726

Answers (1)

Daniel T.
Daniel T.

Reputation: 33967

Here's the simplest I could think of. I made viewModel a global constant but you might want to get more elaborate with it.:

class ViewModel {
    let inputs: [AnyObserver<String>]
    let outputs: [Observable<String>]

    init(count: Int) {
        let subjects = (0..<count).map { _ in BehaviorSubject<String>(value: "") }
        inputs = subjects.map { $0.asObserver() }
        outputs = subjects.map { $0.asObservable() }
    }
}

class ParentViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let bag = self.bag
        Observable.just(viewModel.inputs)
            .bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { _, element, cell in
                let textField = cell.viewWithTag(99) as! UITextField
                textField.rx.text.orEmpty
                    .bind(to: element)
                    .disposed(by: bag)
            }
            .disposed(by: bag)
    }

    let bag = DisposeBag()
}

class ChildViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let bag = self.bag
        Observable.just(viewModel.outputs)
            .bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { _, element, cell in
                element
                    .bind(to: cell.textLabel!.rx.text)
                    .disposed(by: bag)
            }
            .disposed(by: bag)
    }

    let bag = DisposeBag()
}

Upvotes: 3

Related Questions