I have taken one UITableView
and inside that I have used 2 prototype
cells, now in those prototype cells, i have taken 1 UITextField
.<
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
textFieldDidChange(_:)
within the custom UITableViewCell class.textFieldDidChange(_:)
will call the TableViewController's
delegate method cell(cell: UITableViewCell, updatedCustomTextField
textField: UITextField)
to notify the controller that it's text
changed. 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
When you use dequeueReusableCellWithIdentifier
, your cells will be reused. This means when the user scrolls the tableview
, a cell
which moves out of the screen will be reused to display the contents of a cell
which is about to move onto the screen.
Even though this helps in saving memory, the problem it causes is the that the cell
needs to be prepared for display before it is loaded with the content of the new cell
.
In your case, it seems you need to maintain the values the user has entered in a textfield
of a cell
.
So to fix your problem, if there are not that many cells
in the tableview
, simply stop reusing the cell
(Use alloc-init/new for each cell instance). From your code it looks like you have less than 10 cells and this would be the quickest way to fix the issue.
Else, if there are a large number of cells, whenever the user enters a value in the textfield
of a cell
, save it in an array
. And fetch the value from from the array and set it to your textfield
in the cellForRowAtIndexPath
method.