Rxswift 的 UITableView 中的 UISearchBar
UISearchBar in UITableView by Rxswift
我使用 RxSwift 在我的表格视图中显示人员列表,我的表格视图有两个部分,第一个是旧搜索,第二个是所有人员。现在我不知道当用户在 UISearchBar 的文本字段上键入名称时我应该如何过滤人员。
这是我的人物模型:
struct PersonModel {
let name: String
let family:String
let isHistory:Bool
}
这是我的 ContactsViewModel
struct SectionOfPersons {
var header: String
var items: [Item]
}
extension SectionOfPersons: SectionModelType {
typealias Item = PersonModel
init(original: SectionOfPersons, items: [SectionOfPersons.Item]) {
self = original
self.items = items
}
}
class ContactsViewModel {
let items = PublishSubject<[SectionOfPersons]>()
func fetchData(){
var subItems : [SectionOfPersons] = []
subItems.append( SectionOfPersons(header: "History", items: [
SectionOfPersons.Item(name:"Michelle", family:"Obama", isHistory:true ),
SectionOfPersons.Item(name:"Joanna", family:"Gaines", isHistory:true )
]))
subItems.append( SectionOfPersons(header: "All", items: [
SectionOfPersons.Item(name:"Michelle", family:"Obama", isHistory:false ),
SectionOfPersons.Item(name:"James", family:"Patterson", isHistory:false ),
SectionOfPersons.Item(name:"Stephen", family:"King", isHistory:false ),
SectionOfPersons.Item(name:"Joanna", family:"Gaines", isHistory:false )
]))
self.items.onNext( subItems )
}
}
这是我的 ContactsViewController:
class ContactsViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var searchBar: UISearchBar!
private lazy var dataSource = RxTableViewSectionedReloadDataSource<SectionOfPersons>(configureCell: configureCell, titleForHeaderInSection: titleForHeaderInSection)
private lazy var configureCell: RxTableViewSectionedReloadDataSource<SectionOfPersons>.ConfigureCell = { [weak self] (dataSource, tableView, indexPath, contact) in
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ContactTableViewCell", for: indexPath) as? ContactTableViewCell else { return UITableViewCell() }
cell.contact = contact
return cell
}
private lazy var titleForHeaderInSection: RxTableViewSectionedReloadDataSource<SectionOfPersons>.TitleForHeaderInSection = { [weak self] (dataSource, indexPath) in
return dataSource.sectionModels[indexPath].header
}
private let viewModel = ContactsViewModel()
private let disposeBag = DisposeBag()
var showContacts = PublishSubject<[SectionOfPersons]>()
var allContacts = PublishSubject<[SectionOfPersons]>()
override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
viewModel.fetchData()
}
func bindViewModel(){
tableView.backgroundColor = .clear
tableView.register(UINib(nibName: "ContactTableViewCell", bundle: nil), forCellReuseIdentifier: "ContactTableViewCell")
tableView.rx.setDelegate(self).disposed(by: disposeBag)
viewModel.items.bind(to: allContacts).disposed(by: disposeBag)
viewModel.items.bind(to: showContacts).disposed(by: disposeBag)
showContacts.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: disposeBag)
searchBar
.rx.text
.orEmpty
.debounce(0.5, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.filter { ![=12=].isEmpty }
.subscribe(onNext: { [unowned self] query in
////// if my datasource was simple string I cand do this
self.showContacts = self.allContacts.filter { [=12=].first?.hasPrefix(query) } // if datasource was simple array string, but what about complex custome object?!
})
.addDisposableTo(disposeBag)
}
}
感谢您的回复。
您不需要 ContactsViewController
中的两个 PublishSubjects
。您可以将从 UISearchBar 和您的 viewModel 获得的 Observables 直接绑定到您的 UITableView。要使用您的查询过滤联系人,您必须分别过滤每个部分。我为此使用了一些辅助函数。
这就是我所做的
- 删除
showContacts
和 allContacts
属性
- 创建一个
query
Observable 发出用户在搜索栏中输入的文本(不要过滤掉空文本,我们需要它在用户删除文本时恢复所有联系人搜索栏)
- 将
query
Observable 和 viewModel.items
Observable 合并为一个 Observable
- 使用此 observable 来过滤查询中的所有联系人。
- 将 Observable 直接绑定到 table 视图
rx.items
我使用了 combineLatest
,因此只要查询或 viewModel.items 发生变化,table 视图就会更新(我不知道所有联系人的列表是静态的还是您添加/ 删除联系人)。
现在您的 bindViewModel()
代码如下所示(我将 tableView.register(...)
移至 viewDidLoad
):
func bindViewModel(){
let query = searchBar.rx.text
.orEmpty
.distinctUntilChanged()
Observable.combineLatest(viewModel.items, query) { [unowned self] (allContacts, query) -> [SectionOfPersons] in
return self.filteredContacts(with: allContacts, query: query)
}
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
这是使用查询过滤所有联系人的函数:
func filteredContacts(with allContacts: [SectionOfPersons], query: String) -> [SectionOfPersons] {
guard !query.isEmpty else { return allContacts }
var filteredContacts: [SectionOfPersons] = []
for section in allContacts {
let filteredItems = section.items.filter { [=11=].name.hasPrefix(query) || [=11=].family.hasPrefix(query) }
if !filteredItems.isEmpty {
filteredContacts.append(SectionOfPersons(header: section.header, items: filteredItems))
}
}
return filteredContacts
}
我假设您想根据查询检查人员的姓名和家庭。
还有一件事:我删除了 debounce
,因为您过滤了一个已经在内存中的列表,这非常快。当在搜索栏中输入内容触发网络请求时,您通常会使用 debounce
。
我使用 RxSwift 在我的表格视图中显示人员列表,我的表格视图有两个部分,第一个是旧搜索,第二个是所有人员。现在我不知道当用户在 UISearchBar 的文本字段上键入名称时我应该如何过滤人员。
这是我的人物模型:
struct PersonModel {
let name: String
let family:String
let isHistory:Bool
}
这是我的 ContactsViewModel
struct SectionOfPersons {
var header: String
var items: [Item]
}
extension SectionOfPersons: SectionModelType {
typealias Item = PersonModel
init(original: SectionOfPersons, items: [SectionOfPersons.Item]) {
self = original
self.items = items
}
}
class ContactsViewModel {
let items = PublishSubject<[SectionOfPersons]>()
func fetchData(){
var subItems : [SectionOfPersons] = []
subItems.append( SectionOfPersons(header: "History", items: [
SectionOfPersons.Item(name:"Michelle", family:"Obama", isHistory:true ),
SectionOfPersons.Item(name:"Joanna", family:"Gaines", isHistory:true )
]))
subItems.append( SectionOfPersons(header: "All", items: [
SectionOfPersons.Item(name:"Michelle", family:"Obama", isHistory:false ),
SectionOfPersons.Item(name:"James", family:"Patterson", isHistory:false ),
SectionOfPersons.Item(name:"Stephen", family:"King", isHistory:false ),
SectionOfPersons.Item(name:"Joanna", family:"Gaines", isHistory:false )
]))
self.items.onNext( subItems )
}
}
这是我的 ContactsViewController:
class ContactsViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var searchBar: UISearchBar!
private lazy var dataSource = RxTableViewSectionedReloadDataSource<SectionOfPersons>(configureCell: configureCell, titleForHeaderInSection: titleForHeaderInSection)
private lazy var configureCell: RxTableViewSectionedReloadDataSource<SectionOfPersons>.ConfigureCell = { [weak self] (dataSource, tableView, indexPath, contact) in
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ContactTableViewCell", for: indexPath) as? ContactTableViewCell else { return UITableViewCell() }
cell.contact = contact
return cell
}
private lazy var titleForHeaderInSection: RxTableViewSectionedReloadDataSource<SectionOfPersons>.TitleForHeaderInSection = { [weak self] (dataSource, indexPath) in
return dataSource.sectionModels[indexPath].header
}
private let viewModel = ContactsViewModel()
private let disposeBag = DisposeBag()
var showContacts = PublishSubject<[SectionOfPersons]>()
var allContacts = PublishSubject<[SectionOfPersons]>()
override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
viewModel.fetchData()
}
func bindViewModel(){
tableView.backgroundColor = .clear
tableView.register(UINib(nibName: "ContactTableViewCell", bundle: nil), forCellReuseIdentifier: "ContactTableViewCell")
tableView.rx.setDelegate(self).disposed(by: disposeBag)
viewModel.items.bind(to: allContacts).disposed(by: disposeBag)
viewModel.items.bind(to: showContacts).disposed(by: disposeBag)
showContacts.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: disposeBag)
searchBar
.rx.text
.orEmpty
.debounce(0.5, scheduler: MainScheduler.instance)
.distinctUntilChanged()
.filter { ![=12=].isEmpty }
.subscribe(onNext: { [unowned self] query in
////// if my datasource was simple string I cand do this
self.showContacts = self.allContacts.filter { [=12=].first?.hasPrefix(query) } // if datasource was simple array string, but what about complex custome object?!
})
.addDisposableTo(disposeBag)
}
}
感谢您的回复。
您不需要 ContactsViewController
中的两个 PublishSubjects
。您可以将从 UISearchBar 和您的 viewModel 获得的 Observables 直接绑定到您的 UITableView。要使用您的查询过滤联系人,您必须分别过滤每个部分。我为此使用了一些辅助函数。
这就是我所做的
- 删除
showContacts
和allContacts
属性 - 创建一个
query
Observable 发出用户在搜索栏中输入的文本(不要过滤掉空文本,我们需要它在用户删除文本时恢复所有联系人搜索栏) - 将
query
Observable 和viewModel.items
Observable 合并为一个 Observable - 使用此 observable 来过滤查询中的所有联系人。
- 将 Observable 直接绑定到 table 视图
rx.items
我使用了 combineLatest
,因此只要查询或 viewModel.items 发生变化,table 视图就会更新(我不知道所有联系人的列表是静态的还是您添加/ 删除联系人)。
现在您的 bindViewModel()
代码如下所示(我将 tableView.register(...)
移至 viewDidLoad
):
func bindViewModel(){
let query = searchBar.rx.text
.orEmpty
.distinctUntilChanged()
Observable.combineLatest(viewModel.items, query) { [unowned self] (allContacts, query) -> [SectionOfPersons] in
return self.filteredContacts(with: allContacts, query: query)
}
.bind(to: tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
}
这是使用查询过滤所有联系人的函数:
func filteredContacts(with allContacts: [SectionOfPersons], query: String) -> [SectionOfPersons] {
guard !query.isEmpty else { return allContacts }
var filteredContacts: [SectionOfPersons] = []
for section in allContacts {
let filteredItems = section.items.filter { [=11=].name.hasPrefix(query) || [=11=].family.hasPrefix(query) }
if !filteredItems.isEmpty {
filteredContacts.append(SectionOfPersons(header: section.header, items: filteredItems))
}
}
return filteredContacts
}
我假设您想根据查询检查人员的姓名和家庭。
还有一件事:我删除了 debounce
,因为您过滤了一个已经在内存中的列表,这非常快。当在搜索栏中输入内容触发网络请求时,您通常会使用 debounce
。