I am using Xcode 8.2.1 with Swift 3. I have one custom UITableViewCell containing a UITextView. When I type into the textView
, the custom cell automatically grows. This part is working fine. I also want to update my other cells with the same data when I type in a specific cell. For example:
When I type text into the 5th cell, automatically enter the same data in the 11th, 15th and 18th cells. Those cells must be grown automatically as well. I've added data to the specific indexes of the array. But it doesn't get reflected in the tableView
. How do I achieve this functionality?
Please go through following code I've implemented so far. This updateCellHeight
method is a custom delegate method. This method is called when the user types in the 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)
}
I think the issue with automatic data entering is the fact that reloading of cells results in resignFirstResponder()
call.
This seems quite logical as on data reloading you actually give up some cells and ask the table view for new ones through cellForRowAt:
. The old ones might not be the same so they resignFirstResponder()
.
One way to update the text of a cell is to directly alter the cell contents. You can do this for text view contents. Unfortunately for cell height there is no way to directly alter the cell without triggering heightForRowAt:
.
UPDATE2: There IS a solution for iOS 8+ combining direct cell manipulation and AutomaticDimension on the table view.
Working flawlessly for UITextView. Check updates below. This is how the final result looks like:
UPDATE1: Code example for direct cell manipulation added
Use simple model to hold table view data:
// 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)]
And delegate like this:
// Delegate to update visible cells in the table view when text field value change.
protocol TextChangeDelegate {
func textChangedAtCell(_ cell: CustomCell, text: String?)
}
Then init your custom cell:
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
}
In the Custom 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
}
}
This is how to implement the TextChangeDelegate in the view controller with direct cell access, i.e. without reloading data:
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.
}
}
You now have simultaneous text update on all cells with same CellType. This example use UITextField (check UPDATE2 for UITextView).
UPDATE2 goes here. Cell with UITextView with simultaneous text AND height adjustment.
Use the model and controller from previous example (minor changes as you will use new cell). Utilize UITableViewCell and put UITextView inside. In AutoLayout set constraints to text view:
Remember to disable scrolling and bounces to the text view (otherwise you will not have auto-height). Don't forget to link textView's delegate to the UITextViewCell subclass.
Then in the cell, use
func textViewDidChange(_ textView: UITextView) {
self.textChangeDelegate?.textChangedAtCell(self, text: textView.text)
}
Then, super important, in the ViewController's viewDidLoad, add this two settings
tableView.estimatedRowHeight = 50
tableView.rowHeight = UITableViewAutomaticDimension
and register the cell:
tableView.register(UINib(nibName: "CustomTextViewCell", bundle: nil),
forCellReuseIdentifier: "customTextViewCell")
Finally, add this code to TextChangeDelegate implementation from UPDATE1 to do the actual height update trick:
func textChangedAtCell(_ cell: UITableViewCell, text: String?) {
<code from UPDATE 1 goes here>
tableView.beginUpdates()
tableView.endUpdates()
}
Enjoy!
Once you update the data set with the new data. Reload the particular cells with the updated data set.
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)
}
}
}
}
}
NOTE: If the modified data set cells are visible then only we have to reload the particular cells.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With