【问题标题】:Viewing initial data source and filtering with RxSwift using MVVM使用 MVVM 使用 RxSwift 查看初始数据源和过滤
【发布时间】:2019-07-31 08:49:24
【问题描述】:

我从 RxSwift 开始,并试图获得一个使用 UISearchController 过滤数据源的简单示例。

我将 UISearchController 的基本设置连接到 UITableViewController。使用 MVVM,我还有一个基本的视图模型设置来驱动表格。

self.viewModel.searchText.accept(searchController.searchBar.text ?? "")
viewModel.listItems.bind(to: tableView.rx.items(cellIdentifier: "ItemCell")) { row, item, cell in
    cell.textLabel!.text = item.name
}
.disposed(by: disposeBag)

查看模型

class ListViewModel {

    private let sourceItems: BehaviorRelay<[ListItem]> = BehaviorRelay(value: [
            ListItem(name: "abc"),
            ListItem(name: "def"),
            ListItem(name: "ghi"),
            ListItem(name: "jkl"),
            ListItem(name: "mno")
        ])

    let searchText = BehaviorRelay<String>(value: "")
    var listItems: Observable<[ListItem]> = Observable.just([])

    init() {
        listItems = sourceItems.asObservable()
    }

}

我可以添加搜索过滤,这样只有匹配过滤字符串的值才会显示

let searchObservable = searchText
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .distinctUntilChanged()
    .filter { query in
        return query.count > 2
    }
    .share(replay: 1)

listItems = Observable.combineLatest(sourceItems.asObservable(), searchObservable) { items, query in
        return items.filter({ item in
            item.name.lowercased().contains(query.lowercased())
        })
    }

但是,在匹配过滤器之前,它不会显示任何值。我要做的是最初显示所有值,然后只显示过滤后的值。当searchText 更改但为空或过滤掉事件时,我不太确定如何填充listItems

【问题讨论】:

  • 你如何改变你的搜索文本?
  • 您是否尝试过制作 ReplaySubject 类型的 sourceItems? ReplaySubject.create(bufferSize: 1)

标签: swift mvvm rx-swift


【解决方案1】:

您忘记订阅更改,而不是

listItems = Observable.combineLatest(sourceItems.asObservable(), searchObservable) { items, query in
    return items.filter({ item in
        item.name.lowercased().contains(query.lowercased())
    })
}

应该是

        Observable.combineLatest(sourceItems.asObservable(), searchObservable) { items, query in
        return items.filter({ item in
            item.name.lowercased().contains(query.lowercased())
        })
        }.subscribe(onNext: { resultArray in
            print(resultArray) // here you can change your listItems
        })
        .disposed(by: disposeBag)

这是如何更改 searchText searchText.accept("123")

更新:

要处理任何 searchBar 更新,您应该实现 serachBar.rx 下面是一些例子

 import UIKit
import RxSwift
import RxCocoa

class ListItem: NSObject {

    var name: String = ""

    public init(name str: String) {
        super.init()
        name = str
    }

}

class ViewController: UIViewController, UISearchBarDelegate {

    @IBOutlet weak var searchBar: UISearchBar!

    private let sourceItems: BehaviorRelay<[ListItem]> = BehaviorRelay(value: [
        ListItem(name: "abc"),
        ListItem(name: "def"),
        ListItem(name: "ghi"),
        ListItem(name: "jkl"),
        ListItem(name: "mno")
        ])

    let searchText = BehaviorRelay<String>(value: "")
    var listItems: Observable<[ListItem]> = Observable.just([])
    var disposeBag: DisposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        addSearchBarObserver()
        listItems = sourceItems.asObservable()
        Observable.combineLatest(sourceItems.asObservable(), searchText) { items, query in
            return items.filter({ item in
                item.name.lowercased().contains(query.lowercased())
            })
            }.subscribe(onNext: { resultArray in
                print(resultArray)
            })
            .disposed(by: disposeBag)
    }

    private func addSearchBarObserver() {
        searchBar
            .rx
            .text
            .orEmpty
            .debounce(.milliseconds(300), scheduler: MainScheduler.instance)
            .distinctUntilChanged()
            .subscribe { [weak self] query in
                guard
                    let query = query.element else { return }
                self?.searchText.accept(query)
            }
            .disposed(by: disposeBag)
    }

}

【讨论】:

  • 这不太行,我的行为与现有行为相同,即它以空白 listItem 开头。 searchText 是在 UITableViewController 中使用 self.viewModel.searchText.accept(searchController.searchBar.text ?? "") 设置的。我认为问题在于combineLatest 在启动或搜索栏被清空时没有收到任何事件,但我不知道如何让它做我需要的事情
  • 我用完整的代码示例更新了答案,询问是否不清楚
  • 感谢您的帮助,不幸的是它还没有完全到位。当控制器第一次加载或搜索框被清空时,表格是空的。有没有办法在加载和搜索栏为空时显示完整列表?
  • 感谢您的帮助,设法调整您的答案以获得所需的结果
【解决方案2】:
  • 我的方法如下

  • 创建如下的可观察类

import Foundation

class Observable<Generic> {

    var value: Generic {
        didSet {
            DispatchQueue.main.async {
                self.valueChanged?(self.value)
            }
        }
    }

    private var valueChanged: ((Generic) -> Void)?

    init(_ value: Generic) {
        self.value = value
    }

    /// Add closure as an observer and trigger the closure imeediately if fireNow = true
    func addObserver(fireNow: Bool = true, _ onChange: ((Generic) -> Void)?) {
        valueChanged = onChange
        if fireNow {
            onChange?(value)
        }
    }

    func removeObserver() {
        valueChanged = nil
    }

}
  • 在您的 VM 中包含原始列表
  • 如下创建不同的过滤列表
var cellViewModels: Observable<[/* your model */]?>
  • 在视图中有如下的搜索栏委托方法 - 请记住,它会根据您的实现而有所不同 -
class TradeDealsViewController: UIViewController, UISearchBarDelegate {

    // Mark :- IB Outlets
    @IBOutlet weak var collectionView: UICollectionView!

    private var viewModel: ViewModel /* your VM */?

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        viewModel = TradeDealsViewModel()
        bindViewLiveData()
    }

    private func bindViewLiveData(){
        viewModel?.cellViewModels.addObserver({ [weak self] (responsee) in
            self?.collectionView.reloadData()
        })
    }

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        /* do your filtration logic here */
    }

}

  • 请注意,此解决方案不使用 RxSwift,它仅使用基础

【讨论】:

  • 感谢您的回复,但我正在寻找 RxSwift 解决方案
【解决方案3】:

感谢@AlexandrKolesnik,我设法调整了他的答案并让它发挥作用:

class ListViewModel {

    private let sourceItems: BehaviorRelay<[ListItem]> = BehaviorRelay(value: [
        ListItem(name: "abc"),
        ListItem(name: "def"),
        ListItem(name: "ghi"),
        ListItem(name: "jkl"),
        ListItem(name: "mno"),
        ListItem(name: "abcdef")
    ])

    private var filteredItems: BehaviorRelay<[ListItem]> = BehaviorRelay(value: [])

    let searchText = BehaviorRelay<String>(value: "")
    var listItems: Observable<[ListItem]> {
        return filteredItems.asObservable()
    }

    private let disposeBag = DisposeBag()

    init() {
        searchText
            .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
            .distinctUntilChanged()
            .subscribe({ [unowned self] event in

                guard let query = event.element, !query.isEmpty  else {
                    self.filteredItems.accept(self.sourceItems.value)
                    return
                }

                self.filteredItems.accept(
                    self.sourceItems.value.filter {
                        $0.name.lowercased().contains(query.lowercased())
                    }
                )

            })
            .disposed(by: disposeBag)
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-18
    • 2022-01-18
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多