TL;DR (Just give me the code)
The Github link: https://github.com/starkindustries/CustomTableView
The Problem
As others have pointed out, dequeueReusableCellWithIdentifier
will reuse the same cell references when scrolling. Take a look at the gif below for a visual demonstration of this problem.
The table in the gif has 20 rows (more rows than can fit on screen). Each row has a single textfield. I typed in "one", "two", "three", "four" into rows one through four respectively.
As you can see, when I scroll down, "two", "three", and "one" magically appear at the bottom (unwanted behavior). This is because those cells dequeued from the top when scrolled off screen and got reused again on the bottom.

Discussion
After much research on the web and stack overflow, I suggest you store the values of the textfields in an array when they change. Then you can set the textfield text accordingly in the cellForRowAt
method. @Rob perfectly explains how to solve this using the model-view-controller pattern in his answer here: https://stackoverflow.com/a/38272077/2179970. I've implemented his suggestion into this following example.
How It Works
- First, you'll setup a protocol so that the custom UITableViewCell (the view) can send a
message back to the ViewController to notify it that its textfield
changed. ViewController will implement the protocol. The cell will call the protocol method.
- The user of your app will type in text. This will trigger
textFieldDidChange(_:)
within the custom UITableViewCell class.
- The
textFieldDidChange(_:)
will call the TableViewController's
delegate method cell(cell: UITableViewCell, updatedCustomTextField
textField: UITextField)
to notify the controller that it's text
changed.
- The controller will update its text array (the model) to save the
change.
- The
cellForRowAt
will update the cell's textfield's
contents appropriately if it needs to be reused.
The Solution
Protocol:
// Protocol from Step 1
protocol CustomCellDelegate: class {
// This is the function that the ViewController will implement
func cell(cell: UITableViewCell, updatedCustomTextField textField: UITextField)
}
Custom UITableViewCell:
class MyTableViewCell: UITableViewCell, UITextFieldDelegate {
// Protocol reference (Step 1)
weak var delegate: CustomCellDelegate?
@IBOutlet weak var textField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
// (Step 2) Add a "textFieldDidChange" notification method to the text field control.
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: UIControlEvents.editingChanged)
}
// Step 2
func textFieldDidChange(_ textField: UITextField) {
delegate?.cell(cell: self, updatedCustomTextField: textField)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
View Controller:
class MyTableViewController: UITableViewController, CustomCellDelegate {
var myTexts: [String?]!
override func viewDidLoad() {
super.viewDidLoad()
myTexts = Array(repeating: nil, count: 20)
let nib = UINib(nibName: "MyTableViewCell", bundle: nil)
self.tableView.register(nib, forCellReuseIdentifier: "MyTableViewCell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("CellForRowAt: " + indexPath.row.description)
let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath) as! MyTableViewCell
// Step 5: TableView updates cell contents if reused
cell.delegate = self
cell.textField.text = myTexts[indexPath.row]
return cell
}
// MARK: - CustomCellDelegate Method
// Step 3: The cell will call this protocol method to message the controller
func cell(cell: UITableViewCell, updatedCustomTextField textField: UITextField) {
// when the cell tells us that its text field's value changed, update our own model
if let indexPath = tableView.indexPath(for: cell), let string = textField.text {
// Step 4: The controller updates the model:
print("delegate method cell updatedCustomTextField")
myTexts[indexPath.row] = string
}
}
}
The Result
I typed in the same "one", "two", "three", "four" into the first four rows as before. This time, the table behaves as expected: the textfield contents appear where I put them and they don't disappear/appear randomly.

Github Link
Here is the link to the project: https://github.com/starkindustries/CustomTableView
References
Init custom UITableViewCell from nib without dequeueReusableCellWithIdentifier
UITextFields in UITableView, entered values reappearing in other cells
UITextField in UITableViewCell and validation in modal view