Как обновлять каждый пользовательский элемент данных таблицы в одно и то же время?

Я использую Xcode 8.2.1 с Swift 3. У меня есть один пользовательский UITableViewCell, содержащий UITextView. Когда я ввожу в textView, пользовательская ячейка автоматически растет. Эта часть работает нормально. Я также хочу обновить мои другие ячейки теми же данными, когда я ввожу конкретную ячейку. Например:

Когда я ввожу текст в 5-ю ячейку, автоматически вводите те же данные в 11-й, 15-й и 18-й ячейки. Эти клетки также должны выращиваться автоматически. Я добавил данные к конкретным индексам массива. Но это не отражается в tableView. Как достичь этой функциональности?

Прочитайте следующий код, который я реализовал до сих пор. Этот метод updateCellHeight является обычным методом делегирования. Этот метод вызывается, когда пользователь вводит в textView.

func updateCellHeight(indexPath: IndexPath, comment: String) {

    let currentCellLyricVal = self.traduzioneArray[indexPath.row]["rics"]
    tempIndexArray.removeAll()

    for (index,element) in traduzioneArray.enumerated() {

        let lyricVal = element["rics"]
        if currentCellLyricVal == lyricVal {
            tempIndexArray.append(index)
        }
    }
    for index in tempIndexArray {
        self.traduzioneArray[index]["ione"] = comment
        self.lyricsTableView.beginUpdates()
        self.lyricsTableView.endUpdates()
    }
}

func textViewDidChange(_ textView: UITextView) {
    self.delegate.updateCellHeight(indexPath: self.cellIndexPath, comment: textView.text)
}

Ответы

Ответ 1

Я думаю, что проблема с автоматическим вводом данных заключается в том, что перезагрузка ячеек приводит к вызову resignFirstResponder().

Это кажется вполне логичным, так как при перезагрузке данных вы фактически отказываетесь от некоторых ячеек и запрашиваете представление таблицы для новых через cellForRowAt:. Старые могут быть не такими, чтобы они resignFirstResponder().

Одним из способов обновления текста ячейки является прямое изменение содержимого ячейки. Вы можете сделать это для содержимого текстового представления. К сожалению, для высоты ячейки нет возможности напрямую изменять ячейку без запуска heightForRowAt:.

UPDATE2: существует решение для iOS 8+, объединяющее манипуляции с прямыми ячейками и AutomaticDimension в представлении таблицы.
Работает безупречно для UITextView. Проверьте обновления ниже. Вот как выглядит конечный результат:

введите описание изображения здесь

UPDATE1: добавлен пример кода для прямой манипуляции с ячейками

Используйте простую модель для хранения данных табличного представления:

// Simple model, for the example only
class Model {
    // The value of the text field
    var value: String?
    // The type of the cell
    var type: CustomCell.CellType = .typeA

    init(value: String) {
        self.value = value
    }

    init(value: String, type: CustomCell.CellType) {
        self.value = value
        self.type = type
    }
}

// Init the model.
var tableData = [
    Model(value: "Cell Type A", type: .typeA),
    Model(value: "Cell Type B", type: .typeB),
    Model(value: "Cell Type B", type: .typeB),
    Model(value: "Cell Type A", type: .typeA),
    Model(value: "Cell Type B", type: .typeB)]

И выполните следующие действия:

// Delegate to update visible cells in the table view when text field value change.
protocol TextChangeDelegate {
    func textChangedAtCell(_ cell: CustomCell, text: String?)
}

Затем запустите свою настраиваемую ячейку:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    // Will crash if identifier is not registered in IB
    let cell = tableView.dequeueReusableCell(withIdentifier: "customCell") as! CustomCell

    // For this to work you need to update the model on each text field text change
    cell.textValue.text = tableData[indexPath.row].value
    cell.index = indexPath.row
    cell.type = tableData[indexPath.row].type
    cell.textChangeDelegate = self

    return cell
}

В пользовательской ячейке:

class CustomCell: UITableViewCell {

    // The type of the cell. Cells with same type will be updated simultaneously.
    enum CellType {
        case typeA
        case typeB
    }

    // Very simple prototype cell with only one text field in it
    @IBOutlet weak var textValue: UITextField!

    // Default type
    var type = CellType.typeA
    // Index in the model. Not optional (or other special assumptions on initial value)
    // for simplicity of the example.
    var index = 0
    var textChangeDelegate: TextChangeDelegate?
}

extension CustomCell: UITextFieldDelegate {

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

        // Assume we will return true. Any logic could appear here.
        // If we need to return false, don't call the delegate method.
        let result = true

        if result {
            let nsString = textField.text as NSString?
            let newString = nsString?.replacingCharacters(in: range, with: string)
            print("New: \(newString ?? "(nil)")")

            textChangeDelegate?.textChangedAtCell(self, text: newString)
        }

        return result
    }
}

Вот как реализовать TextChangeDelegate в контроллере представления с прямым доступом к ячейке, то есть без перезагрузки данных:

extension ViewController: TextChangeDelegate {

    func textChangedAtCell(_ cell: CustomCell, text: String?) {

        // Only visual update. Skip the cell we are currently editing.
        for visibleCell in tableView.visibleCells where visibleCell != cell {
            if let currentCell = visibleCell as? CustomCell,
                tableData[currentCell.index].type == cell.type {
                    currentCell.textValue.text = text
            }
        }

        // Update the model (including invisible cells too)
        for (index, data) in tableData.enumerated() where data.type == cell.type {
            tableData[index].value = text
        }

        // Avoid reloading here, as this will trigger resignFirstResponder and you will
        // have issues when jumping from text field to text field across cells.
    }
}

Теперь у вас есть одновременное обновление текста для всех ячеек с тем же CellType. В этом примере используйте UITextField (проверьте UPDATE2 для UITextView).

UPDATE2 идет здесь. Ячейка с UITextView с одновременной настройкой текста и высоты.

Используйте модель и контроллер из предыдущего примера (небольшие изменения, поскольку вы будете использовать новую ячейку). Используйте UITableViewCell и поместите UITextView внутри. В AutoLayout установлены ограничения для текстового представления:

Добавить ограничения для автоматического изменения размера

Не забудьте отключить прокрутку и отскакивание в текстовом режиме (иначе у вас не будет автоматической высоты). Не забудьте связать делегат textView с подклассом UITextViewCell.

Затем в ячейке используйте

func textViewDidChange(_ textView: UITextView) {
    self.textChangeDelegate?.textChangedAtCell(self, text: textView.text)
}

Затем, супер важно, в ViewController viewDidLoad добавьте эти две настройки

tableView.estimatedRowHeight = 50
tableView.rowHeight = UITableViewAutomaticDimension

и зарегистрировать ячейку:

tableView.register(UINib(nibName: "CustomTextViewCell", bundle: nil),
                   forCellReuseIdentifier: "customTextViewCell")

Наконец, добавьте этот код в реализацию TextChangeDelegate из UPDATE1, чтобы выполнить фактический трюк с обновлением:

func textChangedAtCell(_ cell: UITableViewCell, text: String?) {

    <code from UPDATE 1 goes here>

    tableView.beginUpdates()
    tableView.endUpdates()
}

Наслаждайтесь!

Ответ 2

После обновления набора данных новыми данными. Перезагрузите отдельные ячейки обновленным набором данных.

func updateCellHeight(indexPath: IndexPath, comment: String) {
        DispatchQueue.main.async {
            let currentCellLyricVal = self.traduzioneArray[indexPath.row]["rics"]
            tempIndexArray.removeAll()

            for (index,element) in traduzioneArray.enumerated() {

                let lyricVal = element["rics"]
                //You have to skip the current cell. by checking index != indexPath.row
                if currentCellLyricVal == lyricVal, index != indexPath.row {
                    tempIndexArray.append(index)
                }
            }

            for index in tempIndexArray {
                self.traduzioneArray[index]["ione"] = comment

                let updatedIndexPath = IndexPath(row: index, section: indexPath.section)
                  if let visibleCellIndexPaths = tableView.indexPathsForVisibleRows  {
                       if visibleCellIndexPaths.contains(updatedIndexPath) {
                            tableView.reloadRows(at: [updatedIndexPath], with: .automatic)
                       }
                  }
            }
        }
    }

ПРИМЕЧАНИЕ.. Если ячейки с измененными ячейками данных видны, мы должны перезагрузить определенные ячейки.

Ответ 3

Во-первых, из раскадровки или в классе tableView Cell задайте текстовые образы NumberOfLines = 0.

В высоте для делегата строки есть

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

В вашем viewdidLoad добавьте следующие строки:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 140

В таблицеViewController

 func updateCellHeight(indexPath: IndexPath, comment: String) {

    let currentCellLyricVal = self.traduzioneArray[indexPath.row]["rics"]
    tempIndexArray.removeAll()

    for (index,element) in traduzioneArray.enumerated() {

        let lyricVal = element["rics"]
        if currentCellLyricVal == lyricVal {
            tempIndexArray.append(index)
        }
    }
    for index in tempIndexArray {
        self.traduzioneArray[index]["ione"] = comment
    }
      tableView.reloadRows(at: [IndexPath(row: 11, section: 1),IndexPath(row: 13, section: 1)], with: UITableViewRowAnimation.none)
}

При наличии линий, установленных в 0, и автоматической размерности высоты ячейки автоматически изменяются. Также убедитесь, что вы настроили свои собственные ячейки делегировать себя в свой cellForRow метод.

Ответ 4

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

func updateCellHeight(indexPath: IndexPath, comment: String) {

  //update dataModel according to new text
  ...
  //then reload visible tableview rows
  //pay atttention to beginUpdates endUpdates methods.
  //This do the trick)

  tableView.beginUpdates()
  tableView.reloadRows(at: indexPathsForVisibleRows ?? [], with: .none)
  tableView.endUpdates()

 }