3

I have taken one UITableView and inside that I have used 2 prototype cells, now in those prototype cells, i have taken 1 UITextField.

Now when I run the app and enter value in first prototype cell then i scroll UITableView, then first prototype cell's textfield value come to last prototype cell's textfield value. so it replace everytime i scrolldown or scroll up. sometime it goes blank.

here is my code

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   if(indexPath.row==0)
   {

    Client_MobileCell *cell     = [tableView dequeueReusableCellWithIdentifier:cellIdentifier_MobileCell forIndexPath:indexPath];
    cell.lblTitle.text          = [arrTitle objectAtIndex:indexPath.row];
    cell.txtMobile.placeholder  = [arrPlaceholder objectAtIndex:indexPath.row];
   // cell.txtMobile.tag = indexPath.row+100;
    return cell;
}
else
{
//        Client_Common_Cell *cell = (Client_Common_Cell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier_CommonCell forIndexPath:indexPath];
    Client_Common_Cell *cell = (Client_Common_Cell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier_CommonCell];

   // cell.txtCommon.tag = indexPath.row+100;
    cell.delegate = self;

    if(indexPath.row == 1 || indexPath.row == 2 || indexPath.row == 3 || indexPath.row == 4){
        if(indexPath.row == 2)
            cell.txtCommon.secureTextEntry = YES;
        else if(indexPath.row == 4){
            cell.txtCommon.keyboardType = UIKeyboardTypeEmailAddress;
        }
        cell.imgArrow.hidden = YES;
        cell.btnSelection.hidden = YES;
    }
    else{
        cell.btnSelection.hidden = NO;
        cell.imgArrow.hidden = NO;
    }
        cell.lblTitle.text       = [arrTitle objectAtIndex:indexPath.row];
        cell.txtCommon.placeholder      = [arrPlaceholder objectAtIndex:indexPath.row];
        return cell;
}

}

Nirav
  • 157
  • 12
  • save the values the NSDictionary and assign it from there, during scrolling. – Fahad Jamal Jun 06 '16 at 07:53
  • @James: How ? can you give me example ? – Nirav Jun 06 '16 at 08:00
  • create nsmutabledictionary , and setObject to dictionary when u are typing data in the textfield, i meant textfield delegate. – Fahad Jamal Jun 06 '16 at 08:05
  • then how to assign those values to cell textfield ? i mean will i have to store dictionary inside nsmutablearray ? – Nirav Jun 06 '16 at 08:10
  • You need to declare a nsmutable array which contains nsmutable dictionary of 2 values. store the text to be displayed against key "text" and store the index of the textfield against key "index". Now in the delegate function of uitextfield, detect in which textfield user is typing by setting the tag of the textfield from cellforrowatindexpath. and store the changed value of textfield in the dictionary for the respective key. Also don't forget to populate the value in the textfield in the cellforrowatindexpath function. tell me if you don't understand still. – Mahesh Agrawal Jun 06 '16 at 08:21
  • @MaheshAgrawal: worked.. thanks a lot – Nirav Jun 06 '16 at 09:04

2 Answers2

3

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.

enter image description here

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

  1. 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.
  2. The user of your app will type in text. This will trigger textFieldDidChange(_:) within the custom UITableViewCell class.
  3. The textFieldDidChange(_:) will call the TableViewController's delegate method cell(cell: UITableViewCell, updatedCustomTextField textField: UITextField) to notify the controller that it's text changed.
  4. The controller will update its text array (the model) to save the change.
  5. 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.

enter image description here

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

Community
  • 1
  • 1
Zion
  • 1,562
  • 13
  • 32
1

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.

7vikram7
  • 2,764
  • 1
  • 25
  • 43