Получать и отправлять наблюдаемые в RxSwift - PullRequest
1 голос
/ 23 января 2020

У меня есть существующий и работающий код, который выглядит следующим образом (ненужные материалы удалены):

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)

Когда вы вводите имя в текстовое поле, он автоматически ищет (через API) друзей, соответствующих этому query.

Но я также хочу добавить еще одну функцию - импорт из контактов, вызываемый дополнительной кнопкой. У меня есть список локальных контактов, и с их помощью я спрашиваю API о результатах поиска. Я хочу отобразить эти результаты в одном и том же месте. Но я не знаю, как отправить эти результаты в tableView. Я думал о создании переменной:

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

Но я не знаю, как отправлять / получать данные. Есть какие-нибудь подсказки?

edit:

Благодаря @andromedainative теперь код выглядит так:

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)

И передать результаты из getContacts просто:

items.accept(data)

Но у меня есть особый случай, когда мне нужно обновить данные sh. Как я могу вызвать queryTextField для вызова API с тем же запросом?

Я нашел только один хакерский способ:

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

Мне нужно изменить какое-то другое значение и вернуть его обратно. Есть ли еще чистый раствор?

Ответы [ 2 ]

0 голосов
/ 23 января 2020

Как правило, когда вы хотите объединить значения, испускаемые двумя или более наблюдаемыми, вам нужно использовать одно из combineLatest, zip, withLatestFrom или merge (возможно, с scan). Это зависит от контекста, который вы должны использовать. Эта статья может помочь: Рецепты для объединения наблюдаемых в RxSwift

Вот идея для вашей конкретной c ситуации. Приведенный ниже код добавляет пару функций, которые вам нужно реализовать: func getContacts() -> Observable<[Contact]> и 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)

Кстати, ваш getContacts не должен быть броском. Его ошибки должны излучаться из наблюдаемой, а не бросать. Кроме того, вы должны использовать debounce в вашем текстовом поле, а не throttle. Последний лучше подходит для типов Void, а не для Strings.

0 голосов
/ 23 января 2020

Вы можете использовать rx для привязки ваших объектов к табличному представлению. Я использую BehaviorRelay для своих массивов, потому что мне нравится иметь возможность доступа к свойству .value элементов.

Это кодировщик с очень псевдо-кодом, поэтому он не будет компилироваться, но я надеюсь, что вы поймете его суть. :

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)
  }

}
...