want to make search filter using RxSwift in iOS

Viewed 236

I have table view that contains list of books I tried to make searchBar and search by book name

enter image description here enter image description here

this is HomeViewModel

private var homeModelSubject      = PublishSubject<[Book]>()
private var filterModelSubject    = PublishSubject<[Book]>()
private var isTableHidden         = BehaviorRelay<Bool>(value: false)
var searchValueBehavior           = BehaviorRelay<String>(value: "")

var homeModelObservable: Observable<[Book]> {
    return homeModelSubject
}

var filterModelObservable: Observable<[Book]> {
    return filterModelSubject
}

var isTableHiddenObservable:Observable<Bool> {
    return isTableHidden.asObservable()
}

var serchValueObservable: Observable<String> {
    return searchValueBehavior.asObservable()
}

init() {
    serchValueObservable.subscribe(onNext: { value in
        self.homeModelObservable.map({ $0.filter ({
            if value.isEmpty {return true}
            return ($0.name.lowercased().contains(value.lowercased()))
        })
        }).bind(to: self.filterModelSubject).disposed(by: self.disposeBag)
    }).disposed(by: disposeBag)
}

this function to make bind to text value

func bindToSearchValue() {
    searchController.searchBar.rx.text.orEmpty.throttle(.milliseconds(500), scheduler: MainScheduler.instance).distinctUntilChanged()
        .bind(to: homeViewModel.searchValueBehavior).disposed(by: disposeBage)
}

this function to subscribe to response

func subscribeToResponse() {
    homeViewModel.filterModelObservable.bind(to: self.tableView.rx.items(cellIdentifier: HomeTableViewCell.reuseIdentifier, cellType: HomeTableViewCell.self)) { row,books,cell in
        cell.separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 20, right: UIScreen.main.bounds.width)
        cell.titleLabel.text           = books.name
        cell.secondaryTitleLabel.text  = books.type
        cell.selectionStyle            = .none
        
        if books.available == true {
            cell.avalibaleOrNotLabel.text = "Avalibale"
            cell.avalibaleOrNotStatus.backgroundColor = .green
        } else {
            cell.avalibaleOrNotLabel.text = "Not Avalibale"
            cell.avalibaleOrNotStatus.backgroundColor = .gray
        }
        
    }.disposed(by: disposeBage)
}

the problem ----> when make search did not filter the cell

1 Answers

The essence of functional reactive programming is to specify the dynamic behavior of a value completely at the time of declaration. -- Heinrich Apfelmus

Your view model should look like this:

class HomeViewModel {
    let filterModelObservable: Observable<[Book]>
    var searchValueObserver: AnyObserver<String?> { searchValueBehavior.asObserver() }
    var homeModelObservable: Observable<[Book]> { homeModelSubject }
    var isTableHiddenObservable:Observable<Bool> { isTableHidden.asObservable() }
    private let homeModelSubject = PublishSubject<[Book]>()
    private let isTableHidden = BehaviorRelay<Bool>(value: false)
    private let searchValueBehavior = BehaviorSubject<String?>(value: "")

    init() {
        filterModelObservable = Observable.combineLatest(
            searchValueBehavior
                .map { $0 ?? "" }
                .startWith("")
                .throttle(.milliseconds(500), scheduler: MainScheduler.instance),
            homeModelSubject
        )
        .map { searchValue, books in
            searchValue.isEmpty ? books : books.filter { $0.name.lowercased().contains(searchValue.lowercased()) }
        }
    }
}

Notice how the values that filterModelObservable will emit are fully specified in the code that creates it.

You shouldn't need so many Subjects:

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

Also note, there is no disposeBag. You shouldn't need a dispose bag in your view model or you are probably doing something wrong.

You should minimize as much as possible the logic contained in your view controller. It should only contain assignments and binds. So your bindToSearchBar should look like this:

func bindToSearchValue() {
    searchController.searchBar.rx.text
        .bind(to: homeViewModel.searchValueObserver)
        .disposed(by: disposeBage)
}

(You probably want to fix the typo in disposeBage as well.)

Related