UISearchBar в UITableView от Rxswift - PullRequest
       43

UISearchBar в UITableView от Rxswift

0 голосов
/ 11 декабря 2018

Я использую RxSwift для отображения списка лиц в моем табличном представлении, и мое табличное представление состоит из двух разделов: первый - это старые запросы, а второй - все люди.теперь я не знаю, как мне фильтровать людей, когда пользователи вводят имя в текстовом поле UISearchBar.

Это моя модель Person:

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 { !$0.isEmpty }
            .subscribe(onNext: { [unowned self] query in

                ////// if my datasource was simple string I cand do this
                self.showContacts = self.allContacts.filter { $0.first?.hasPrefix(query) } // if datasource was simple array string, but what about complex custome object?!

            })
            .addDisposableTo(disposeBag)

    }
}

Спасибо за ваш ответ.

1 Ответ

0 голосов
/ 11 декабря 2018

Вам не нужны два PublishSubjects в вашем ContactsViewController.Вы можете привязать Observables, которые вы получаете от UISearchBar и вашей viewModel, непосредственно к вашему UITableView.Чтобы отфильтровать контакты по вашему запросу, вы должны отфильтровать каждый раздел отдельно.Для этого я использовал небольшую вспомогательную функцию.

Итак, вот что я сделал

  1. Избавьтесь от свойств showContacts и allContacts
  2. Создайтеquery Наблюдаемый, который испускает текст, который пользователь ввел в панель поиска (не фильтруйте пустой текст, нам нужно, чтобы вернуть все контакты, когда пользователь удаляет текст в панели поиска)
  3. Объедините query Наблюдаемую и viewModel.items Наблюдаемую в одну Наблюдаемую
  4. Используйте эту наблюдаемую для фильтрации всех контактов с запросом.
  5. Свяжите это Наблюдаемое непосредственно с представлением таблицы rx.items

Я использовал combineLatest, поэтому представление таблицы обновляется всякий раз, когда изменяется запрос или viewModel.items (я не знаю, является ли этот список всех контактов статическим или если вы добавляете / удаляете контакты).

Так что теперь ваш код 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 { $0.name.hasPrefix(query) || $0.family.hasPrefix(query) }
        if !filteredItems.isEmpty {
            filteredContacts.append(SectionOfPersons(header: section.header, items: filteredItems))
        }
    }
    return filteredContacts
}

Я предположил, что вы хотелипроверить имя и фамилию людей по запросу.

Еще одна вещь: я удалил debounce, потому что вы фильтруете список, который уже находится в памяти, и это действительно быстро.Обычно вы используете debounce, когда ввод в строку поиска вызывает сетевой запрос.

...