Ответ 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()
}
Наслаждайтесь!