UITableView reloadData () вызывает перезагрузку в UISearchBar внутри каждого раздела - PullRequest
0 голосов
/ 07 февраля 2020

У меня есть таблица, которая имеет 2 раздела. Оба раздела имеют UISearchBar в indexPath.row 0, а остальные строки в каждом разделе заполняют список массивов. Всякий раз, когда я набираю какой-то текст в строке поиска, каждый раз, когда вызывается метод делегата searchBar(_ searchBar: UISearchBar, textDidChange searchText: String), а внутри метода делегата я вызываю tableView.reloadData(), чтобы перезагрузить результаты поиска в tableview.

tableview

Теперь проблема в том, что каждый раз, когда tableView перезагружает также и UISearchBar (поскольку UISearchbar находится в строке номер 1), и каждый раз, когда клавиатура SearchBar уходит в отставку.

Вместо выполнения tableView.reloadData() Я даже пытался перезагрузить каждую строку, кроме первой, используя приведенный ниже код

let allButFirst = (self.tableView.indexPathsForVisibleRows ?? []).filter { $0.section != selectedSection || $0.row != 0 }
self.tableView.reloadRows(at: allButFirst, with: .automatic)

Но не повезло. Приложение аварийно завершает работу, говоря

Завершение работы приложения из-за необработанного исключения «NSInternalInconsistencyException», причина: «попытка вставить строку 2 в раздел 0, но в разделе 0 после обновления есть только 2 строки»

Ответы [ 3 ]

1 голос
/ 07 февраля 2020

Возможно, вы меняете источник данных, а затем перезагружаете строки по индексным путям, которых еще нет.

Это не так просто, но давайте рассмотрим пример: перед тем, как начать вводить, результат поиска будет содержать что-то вроде этого:

["aa", "ab", "ba", "bb"]

Затем вы введете "a" в строку поиска и источник данных изменится на: ["aa", "ab"]

tableView.deleteRows(at: [IndexPath(row:3, section: 0), IndexPath(row:4, section: 0)], with: .automatic)

, затем вы удаляете все в этой строке поиска, и ваш источник данных изменится на значение по умолчанию: ["aa", "ab", "ba", "bb"]

, поэтому в этом случае вам нужно позвонить:

tableView.insertRows(at: [IndexPath(row:3, section: 0), IndexPath(row:4, section: 0)], with: .automatic) 

Я создал несколько рабочих пример - без источника раскадровки, я думаю, довольно просто воссоздать его в соответствии с этим классом.

class SearchCell: UITableViewCell {
    @IBOutlet weak var textField:UITextField?
}

class TextCell: UITableViewCell {
    @IBOutlet weak var label:UILabel?
}

class ViewController: UIViewController, UITableViewDataSource, UITextFieldDelegate {

    @IBOutlet weak var tableView: UITableView?
    weak var firstSectionTextField: UITextField?
    var originalDataSource:[[String]] = [["aa","ab","ba","bb"], ["aa","ab","ba","bb"]]
    var dataSource:[[String]] = []
    let skipRowWithSearchInput = 1

    override func viewDidLoad() {
        super.viewDidLoad()
        dataSource = originalDataSource
        tableView?.tableFooterView = UIView()
        tableView?.tableHeaderView = UIView()
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return dataSource.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource[section].count + skipRowWithSearchInput
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if indexPath.row == 0, let cell = tableView.dequeueReusableCell(withIdentifier: "search", for: indexPath) as? SearchCell {
            cell.textField?.removeTarget(self, action: #selector(textFieldDidChangeText(sender:)), for: .editingChanged)
            cell.textField?.addTarget(self, action: #selector(textFieldDidChangeText(sender:)), for: .editingChanged)
            if indexPath.section == 0 {
                firstSectionTextField = cell.textField
            }
            return cell
        } else if let cell = tableView.dequeueReusableCell(withIdentifier: "text", for: indexPath) as? TextCell  {
            cell.label?.text = dataSource[indexPath.section][indexPath.row - skipRowWithSearchInput]
            return cell
        } else {
            return UITableViewCell()
        }
    }

    @objc func textFieldDidChangeText(sender: UITextField) {

        let section = sender == firstSectionTextField ? 0 : 1
        let text = sender.text ?? ""
        let oldDataSource:[String] = dataSource[section]
        //if the search bar is empty then use the original data source to display all results, or initial one
        let newDataSource:[String] = text.count == 0 ? originalDataSource[section] : originalDataSource[section].filter({$0.contains(text)})
        var insertedRows:[IndexPath] = []
        var deletedRows:[IndexPath] = []
        var movedRows:[(from:IndexPath,to:IndexPath)] = []

        //resolve inserted rows
        newDataSource.enumerated().forEach { (tuple) in let (toIndex, element) = tuple
            if oldDataSource.contains(element) == false {
                insertedRows.append(IndexPath(row: toIndex + skipRowWithSearchInput, section: section))                    
            }
        }

        //resolve deleted rows
        oldDataSource.enumerated().forEach { (tuple) in let (fromIndex, element) = tuple
            if newDataSource.contains(element) == false {
                deletedRows.append(IndexPath(row: fromIndex + skipRowWithSearchInput, section: section))
            }
        }

        //resolve moved rows
        oldDataSource.enumerated().forEach { (tuple) in let (index, element) = tuple
            if newDataSource.count > index, let offset = newDataSource.firstIndex(where: {element == $0}), index != offset {
                movedRows.append((from: IndexPath(row: index + skipRowWithSearchInput, section: section), to: IndexPath(row: offset + skipRowWithSearchInput, section: section)))
            }
        }

        //now set dataSource for uitableview, right before you are doing the changes
        dataSource[section] = newDataSource
        tableView?.beginUpdates()
        if insertedRows.count > 0 {
            tableView?.insertRows(at: insertedRows, with: .automatic)
        }
        if deletedRows.count > 0 {
            tableView?.deleteRows(at: deletedRows, with: .automatic)
        }
        movedRows.forEach({
            tableView?.moveRow(at: $0.from, to: $0.to)
        })

        tableView?.endUpdates()
    }
}

результат:

Reloading Specific row in table View

Если вам нужно что-то уточнить, не стесняйтесь спрашивать в комментарии.

0 голосов
/ 12 февраля 2020

Это сработало. Я взял два раздела, один для поля поиска и другой для перезагрузки данных (строки, заполняющие данные). Я взял отдельную пользовательскую ячейку для поиска и выбрал выход в этом классе. и в viewForHeaderInSection я использовал tableView.dequeueReusableCell (withIdentifier :) и возвратил customCell.contentView. Затем я вызвал tableview.ReloadData () в searchBar (_ searchBar: UISearchBar, textDidChange searchText: String). Это сработало без проблем.

0 голосов
/ 07 февраля 2020

Попробуйте это-

tableView.beginUpdates()
//Do the update thing
tableView.endUpdates()
...