In custom Prototype Cell in UITableView, UITextField value replaces with other Prototype Cell

前端 未结 2 816
误落风尘
误落风尘 2021-01-06 18:24

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

相关标签:
2条回答
  • 2021-01-06 18:43

    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

    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.

    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

    0 讨论(0)
  • 2021-01-06 18:52

    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.

    0 讨论(0)
提交回复
热议问题