1
votes

I have existing and working code, which looks like this (unnecessary stuff removed):

queryTextField.rx.text.orEmpty
            .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
            .distinctUntilChanged()
            .flatMapLatest { query in
                return try AddFriendViewController.getSearchResults(query)
                    .retry(3)
                    .startWith([])
                    .catchErrorJustReturn([])
            }
            .bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: AddFriendTableViewCell.self)) { (row, item, cell) in                
                cell.nameLabel.text = "\(item.firstName) \(item.lastName)"
            }
            .disposed(by: disposeBag)

When you type a name in a text field it automatically searches (through an API) for friends matching this query.

But I also want to add another feature - import from contacts, invoked by additional button. I have a list of local contacts and using them I ask API for search results. I want to display these results in the same place. But I don't know how to send these results into tableView. I thought of creating a varabiable:

var items: Observable<[FriendSearchResult]> = Observable.just([])

But I don't know how to send/receive data. Any clues?

edit:

Thanks to @andromedainative now code looks like this:

var items: BehaviorRelay<[FriendSearchResult]> = BehaviorRelay(value: [])

items.asObservable()
        .bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: AddFriendTableViewCell.self)) { (row, item, cell) in                
            cell.nameLabel.text = "\(item.firstName) \(item.lastName)"
        }
        .disposed(by: disposeBag)

queryTextField.rx.text.orEmpty
        .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
        .distinctUntilChanged()
        .flatMapLatest { query in
            return try AddFriendViewController.getSearchResults(query)
                .retry(3)
                .startWith([])
                .catchErrorJustReturn([])
        }
        .bind(to: items) //this was a missing piece
        .disposed(by: disposeBag)

And passing results from getContacts is simple:

items.accept(data)

But, I have a special case where I have to refresh data. How can I invoke queryTextField to call API with the same query?

I've only found a hacky way:

let query = queryTextField.text ?? ""
queryTextField.text = ""
queryTextField.sendActions(for: .valueChanged)
queryTextField.text = query
queryTextField.sendActions(for: .valueChanged)

I have to change to some other value and change it back. Is there any more clean solution?

2

2 Answers

0
votes

You can definitly use rx to bind your objects to your table view. I use BehaviorRelay for my arrays because I like to be able to access the .value property of the items.

This is a very psuedo code anser so it won't compile but I hope you get the gist of it:

class MyViewController: UITableViewController {

  private let disposeBag = DisposeBag()

  var myItems: BehaviorRelay<[FeedItem]>

  func bindData() {

    tableView.rx.setDelegate(self).disposed(by: disposeBag)

    myItems.asObservable()
        .bind(to: tableView.rx.items) { [weak self] tableView, row, myItemType in
            guard let `self` = self else { return UITableViewCell() }
            let indexPath = IndexPath(row: row, section: 0)
            let myItem = self.myItems.value[indexPath.row]
            let myCell = tableView.dequeueCell(reuseIdentifier: "MyIdentifier", for: indexPath) as! MyCellType

            return myCell
    }.disposed(by: disposeBag)
  }

}
0
votes

In general, when you want to combine the values emitted by two or more observables, you need to use one of combineLatest, zip, withLatestFrom, or merge (possibly with scan). It depends on the context which one you should use. This article might help: Recipes for Combining Observables in RxSwift

Here's an idea for your specific situation. The code below adds a couple of functions that you will need to implement: func getContacts() -> Observable<[Contact]> and func getSearchResults(_ contact: Contact) -> Observable<[Friend]>.

let singleSearchResult = queryTextField.rx.text.orEmpty
    .debounce(.milliseconds(300), scheduler: MainScheduler.instance)
    .distinctUntilChanged()
    .flatMapLatest { query in
        return try getSearchResults(query)
            .retry(3)
            .startWith([])
            .catchErrorJustReturn([])
    }

let contactsSearchResult = checkContactsButton.rx.tap
    .flatMapLatest { getContacts() }
    .flatMapLatest {
        Observable.combineLatest($0.map {
            getSearchResults($0)
                .catchErrorJustReturn([])
        })
        .startWith([])
    }
    .map { $0.flatMap { $0 } }

Observable.merge(singleSearchResult, contactsSearchResult)
    .bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: AddFriendTableViewCell.self)) { (row, item, cell) in
        cell.nameLabel.text = "\(item.firstName) \(item.lastName)"
}
.disposed(by: disposeBag)

BTW, your getContacts should not be throwing. Its errors should emit from the Observable, not throw. Also, you should use debounce on your text field, not throttle. The latter is better suited to Void types, not Strings.