RxSwift - trigger search programmatically in UISearchBar

Viewed 6567

I am observing UISearchBar.rx.text attributes to perform some search related Action when User types some text. But at some time, I also would like to trigger this search Action programmatically. For instance at the creation of the view like in this example, where unfortunately the "Searching for [...]" text is not printed.

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {
  @IBOutlet weak var mySearchBar: UISearchBar!

  let disposeBag = DisposeBag()

  override func viewDidLoad() {
    super.viewDidLoad()
    // Trigger search when text changes
    mySearchBar.rx.text.subscribe(onNext: { (text)
         print("Searching for \(text)...")
         // do some search Action
    }
    .disposed(by: disposeBag)

    // Programmatically trigger a search
    mySearchBar.text = "Some text to search"
  }

}

The problem is changing mySearchBar.text does not trigger a new rx.text Event. Is there some way to do so?

For instance, I know thanks to this post that with an UITextField, this is possible using the UITextField.sendActions(for: .ValueChanged) function. Is there some similar way to do so with UISearchBar?

3 Answers

You could use a Variable<String?> as a sink for your search bar updates. That way you could also set its value programmatically, and use the variable instead of the search bar directly to drive your action:

class ViewController: UIViewController {
    let searchText = Variable<String?>(nil)
    let searchBar = UISearchBar()
    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        searchBar.rx.text.asDriver()
            .drive(searchText)
            .disposed(by: disposeBag)

        searchText.asObservable().subscribe(onNext: { [weak self] (text) in
            if let welf = self, welf.searchBar.text != text {
                welf.searchBar.text = text
            }
        })
            .disposed(by: disposeBag)

        searchText.value = "variables so cool"
        searchText.asObservable().subscribe(onNext: { [weak self] (text) in
            self?.doStuff(text)
        })
        .disposed(by: disposeBag)
    }
}

This is a simple usage of Drivers

import UIKit
import RxCocoa
import RxSwift
class SearchViewController: UIViewController {

    @IBOutlet weak var searchBar: UISearchBar!
    let disposeBag = DisposeBag()
    override func viewDidLoad() {
        super.viewDidLoad()
        let dataModels = Driver.just(["David", "Behrad","Tony","Carl","Davidov"])
        let searchingTextDriver = searchBar.rx.text.orEmpty.asDriver()
        let matchedCases = searchingTextDriver.withLatestFrom(dataModels){ searchingItem,models in
            return models.filter({ (item) -> Bool in
                 return item.contains(searchingItem)
            })
        }
        matchedCases.do(onNext: { (items) in
            print(items)            
        }).drive().disposed(by: disposeBag)
    }
}

As you see we created a dataModels Driver that it could be a response of your network or dataBase fetching. the searching text converted to Driver then mixed with the dataModels to create matchedCases.

Yes same works for UITextBar.

Setting .text programmatically does not trigger the action automatically, I don't know why! but this is the case for UITextField too.

Call sendActions after each time you update .text property of a UIView subclass.

Try this :

// Programmatically trigger a search
    mySearchBar.text = "Some text to search"    
    mySearchBar.sendActions(for: .valueChanged)
Related