Search code examples
iosswiftuitableviewuisearchbarrx-swift

How to bind UISearchBar to UITableView with RxSwift


I have an array of numbers, and I want the tableview to show the number(s) that I entered in searchbar. This is my code, I don't know what shall I write in the line after (instead of) .subscribe

let dataSource = BehaviorRelay(value: [String]())
let disposeBag = DisposeBag()
var numbers = ["1", "2", "3", "4", "5"]

override func viewDidLoad() {
    super.viewDidLoad()
    
    dataSource.accept(numbers)
    
    searchBar.rx.text.orEmpty
        .throttle(.milliseconds(2000), scheduler: MainScheduler.instance)
        .filter{self.numbers.contains($0)}
        .subscribe(onNext: 
            
        })
    self.dataSource
        .asObservable()
        .bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: TableViewCell.self)) {(row, element, cell) in
            cell.textLabel?.text = element
        }
        .disposed(by: disposeBag)

Maybe it doesn't work because of other reasons, anyway thank you for help


Solution

  • UPDATED:

    You want to look at reactive code as a cause->effect chain. Some side-effect happens that causes some other side-effect. Of course between the cause and effect you have business logic.

    Note firstly, that there is no need for the Subject. In the book Intro To Rx we learn:

    Subjects provide a convenient way to poke around Rx, however they are not recommended for day to day use.

    Now that we are thinking "cause to effect"... What is the effect we want to achieve? That would be the output of the table view which:

    • adds a number whenever the user taps the add button,
    • removes the last number whenever the user taps the delete button,
    • and filters the list whenever the user enters a number in the text field...

    That's three causes and the logic defining how the causes affect the effect.

    final class Example: UIViewController {
        var searchBar: UISearchBar!
        var addButton: UIButton!
        var deleteButton: UIButton!
        var tableView: UITableView!
        let disposeBag = DisposeBag()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            enum Input {
                case add
                case delete
            }
    
            let initial = ["1", "2", "3", "4", "5"] // this is what we start with
            Observable.merge(
                addButton.rx.tap.map { Input.add }, // flag addButton taps for adding a number
                deleteButton.rx.tap.map { Input.delete } // flag deleteButton taps for deleting a number
            )
            .scan(into: initial) { state, input in // setup a state machine that:
                switch input {
                case .add:
                    state.append("\(state.count + 1)") // adds a number when the add event happens
                case .delete:
                    state.removeLast() // removes the last number when a delete event happens
                }
            }
            .withLatestFrom(searchBar.rx.text) { list, text in
                list.filter { $0 == text } // here we filter the list based on the value in the search bar
            }
            .startWith(initial) // show the initial values at start
            .bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: UITableViewCell.self)) { _, element, cell in
                cell.textLabel?.text = element
            }
            .disposed(by: disposeBag)
        }
    }
    

    You may want to make the filter more or less complex than what I have above. To make the above testable, just move the closures out into separate functions so they can be tested independently.