ReactiveCocoa / ReactiveSwift

Streams of values over time
MIT License
3k stars 431 forks source link

Simple array search filter #634

Closed thundercatchris closed 6 years ago

thundercatchris commented 6 years ago

Hi, is there a simple example of just binding a search textField to a tableView array?

In my case I don't need a new network request each time the textField changes, I just need to filter the returned array and update the tableview when a character is typed

I looked at the example network request, but wasn't sure how to adjust it for this purpose

sharplet commented 6 years ago

Let's say you start with two mutable properties, one for the unfiltered array, and another for the current filter:

let items: MutableProperty<[Foo]> = // ...
let filter = MutableProperty("")

You can use the binding operator to connect your search bar to your filter property:

filter <~ searchBar.reactive.textValues.map { $0 ?? "" }

Then you can compose these two properties together to produce a new property of filtered results:

let filteredItems = items.combineLatest(with: filter).map { items, filterText -> [Foo] in
  if filterText.isEmpty {
    return items
  } else {
    return items.filter { $0.localizedStandardContains(filterText) }
  }
}
// add .skipRepeats() if your items are Equatable

Binding filteredItems to your table view can be achieved by implementing your data source methods to access filteredItems, and then adding one additional binding to trigger reloadData when the filtered items change:

tableView.reactive.reloadData <~ filteredItems.signal.map { _ in } // Signal<(), NoError> that fires whenever the property changes

This may not quite compile as-is, but hopefully that gives you enough to get started.

thundercatchris commented 6 years ago

Hi, thanks for your help. I think Im getting somewhere with this now. But I'm getting an error

" Value of type 'UISearchBar' has no member 'reactive' " "Value of type 'UITableView' has no member 'reactive'"

  let searchController = UISearchController(searchResultsController: nil)
    @IBOutlet weak var tableView: UITableView!
    var products = MutableProperty<[Product]>([])
    let filter = MutableProperty("")

    var ref: DatabaseReference!

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        tableView.dataSource = self

        searchController.searchResultsUpdater = self
        searchController.hidesNavigationBarDuringPresentation = false
        searchController.dimsBackgroundDuringPresentation = false
        searchController.searchBar.sizeToFit()
        self.tableView.tableHeaderView = searchController.searchBar

        filter <~ searchController.searchBar.reactive.textValues.map { $0 ?? "" }

        let filteredItems = products.combineLatest(with: filter).map { products, filterText -> [Product] in
            if filterText.isEmpty {
                return products
            } else {
                return products.filter { $0.name!.localizedStandardContains(filterText) }
            }
        }

    tableView.reactive.reloadData <~ filteredItems.signal.map { _ in }

@sharplet are you able to help please?

mdiep commented 6 years ago

.reactive comes from ReactiveCocoa. Are you using it? Have you imported it?

thundercatchris commented 6 years ago

Hi yes I imported it and thought it was working, but I had made a mistake with the path I was installing it to. Please ignore that last one :S

So now the code is compiling. But I don't think Ive got it wired up correctly, I have the filter code in view did load. I have put break points in to both returns. They trigger on load, but not when I type anything in to the searchfield?

I have also tried initialising an empty property to assign the search results to var filteredItems = Property.init(value: [Product]()) im then assigning to it in viewdidload

But as mentioned before the filter is not being triggered anyway

thundercatchris commented 6 years ago

I have put a breakpoint in updateSearchResults The breakpoint is triggering, but the filter is still ""

So I don't think the binding is working

func updateSearchResults(for searchController: UISearchController) {
    print(searchController.searchBar.text)
}
thundercatchris commented 6 years ago

Got it working. Thanks for your help. I changed textValues to continuousTextValues and moved the code to viewDidAppear

filter <~ searchController.searchBar.reactive.continuousTextValues.map { $0 ?? "" }