How to simultaneously change textField.text in a prototype cell?

1.2k Views Asked by At

I have a UITableViewController and a Prototype cell with a UITextField. When I change a textField.text in one of the cells I want it to be changed in all other cells which my tableView now have (for example multiply number by 2 and set to other cell's textField.text output).

This is the behaviour I want to implement: CLICK

Should I trigger a certain textField delegate method or it should be made in another way?

My code for now:

 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  cell.numberTextField.delegate = self
 }

//Delegate methods

func textFieldDidBeginEditing(_ textField: UITextField) {
    textField.text = ""
    textField.textColor = UIColor.systemBlue
}

func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
        textField.text = "0"
        textField.textColor = UIColor.black
}
2

There are 2 best solutions below

1
Shawn Frank On BEST ANSWER

At a high level:

  1. You need to have a model to store your data being updated
  2. You need to observe the text being entered using UITextFieldDelegate
  3. As your text is being entered, update all rows except active cell with new calculations

The most interesting work is in textFieldDidChangeSelection and cellForRowAt indexPath

Here is an example of how I would do something like this:

1. Set up some custom types

// Your model to store data in text fields
// Maybe for you, you will store currencies
// and base currency to multiply against
// I am just storing a random number for example
struct FinanceModel
{
    var number: Int?
}

// Custom UITextField which will store info
// about cell index path as we need to identify
// it when editing
class MappedTextField: UITextField
{
    var indexPath: IndexPath!
}

2. Example of UITableView Cell, you can ignore if you have your own

fileprivate class CustomCell: UITableViewCell
{
    // Use custom text field
    var textField: MappedTextField!
    
    static let tableViewCellIdentifier = "cell"
    
    override init(style: UITableViewCell.CellStyle,
                  reuseIdentifier: String?)
    {
        super.init(style: style,
                   reuseIdentifier: reuseIdentifier)
        
        configureTextField()
    }
    
    required init?(coder: NSCoder)
    {
        fatalError("init(coder:) has not been implemented")
    }
    
    // Configure text field and auto layout
    private func configureTextField()
    {
        textField = MappedTextField()
        textField.keyboardType = .numberPad
        textField.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(textField)
        
        textField.layer.borderWidth = 2.0
        textField.layer.borderColor = UIColor.blue.cgColor
        textField.layer.cornerRadius = 5.0
        
        // auto-layout
        textField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor,
                                           constant: 20).isActive = true
        textField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor,
                                            constant: -20).isActive = true
        textField.topAnchor.constraint(equalTo: contentView.topAnchor,
                                       constant: 20).isActive = true
        textField.bottomAnchor.constraint(equalTo: contentView.bottomAnchor,
                                          constant: -20).isActive = true
    }
}

3. Set up in view controller - you can skip if already set up, just here for completeness

class InputViewController: UIViewController
{
    let tableView = UITableView()
    
    // Initialize your mode
    var financeModel = FinanceModel()
    
    // This will store current editing cell which is active
    var activeTextFieldIndexPath: IndexPath?
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        // This is just view set up for me,
        // You can ignore this
        title = "TableView Input"
        navigationController?.navigationBar.tintColor = .white
        view.backgroundColor = .white
        
        configureTableView()
    }
    
    // Configure TableView and layout
    private func configureTableView()
    {
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.register(CustomCell.self,
                           forCellReuseIdentifier: CustomCell.tableViewCellIdentifier)
        
        tableView.dataSource = self
        tableView.delegate = self
        
        // remove additional rows
        tableView.tableFooterView = UIView()
        
        view.addSubview(tableView)
        
        // Auto layout
        tableView.leadingAnchor
            .constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
        tableView.topAnchor
            .constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        tableView.trailingAnchor
            .constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
        tableView.bottomAnchor
            .constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
    }
}

4. TableView DataSource

extension InputViewController: UITableViewDataSource
{
    func tableView(_ tableView: UITableView,
                   numberOfRowsInSection section: Int) -> Int
    {
        // Random number for me
        return 10
    }
    
    func tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath) -> UITableViewCell
    {
        let cell =
            tableView.dequeueReusableCell(withIdentifier:
                                            CustomCell.tableViewCellIdentifier)
            as! CustomCell
        
        // Set index path of custom text field
        cell.textField.indexPath = indexPath
        
        // Set view controller to respond to text field events
        cell.textField.delegate = self
        
        // Check if any cell is currently active
        // && if we are current cell is NOT the active one
        // We want to leave active cell untouched
        if let activeTextFieldIndexPath = activeTextFieldIndexPath,
           activeTextFieldIndexPath.row != indexPath.row
        {
            updateCell(cell, atIndexPath: indexPath)
        }
        else
        {
            // no active cell, just set the text
            updateCell(cell, atIndexPath: indexPath)
        }

        return cell
    }
    
    private func updateCell(_ cell: CustomCell,
                            atIndexPath indexPath: IndexPath)
    {
        // Retrieve number currently stored in model
        if let number = financeModel.number
        {
            // Set text of number in model * row number
            // Do any calculation you like, this is just an
            // example
            cell.textField.text =
                "\(number) x \(indexPath.row) = \(number * indexPath.row)"
        }
        else
        {
            // If no valid number, set blank
            cell.textField.text = ""
        }
    }
}

5. TextField Delegate

extension InputViewController: UITextFieldDelegate
{
    // Respond to new text in the text field
    func textFieldDidChangeSelection(_ textField: UITextField)
    {
        // 1. Convert generic UITextField to MappedTextField
        // 2. && Retrieve index path from custom MappedTextField
        // 3. && Retrieve text from the text field
        // 4. && Check if text is valid number
        if let textField = textField as? MappedTextField,
           let indexPath = textField.indexPath,
           let text = textField.text,
           let number = Int(text)
        {
            // Assign local variable with index path we are editing
            activeTextFieldIndexPath = indexPath
            
            // Update number in the financial model
            financeModel.number = number
            
            // We only want to update data in visible rows so
            // get all the index paths of visible rows
            let visibleRows = self.tableView.indexPathsForVisibleRows ?? []
            
            // We want to update all rows EXCEPT active row
            // so do a filter for this
            let allRowsWithoutActive = (visibleRows).filter
            {
                    // Remove the active index path from the
                    // visible index paths
                    $0.section != indexPath.section ||
                        $0.row != indexPath.row
            }
            
            // Reload the visible rows EXCEPT active
            self.tableView.reloadRows(at: allRowsWithoutActive,
                                      with: .automatic)
        }
    }
    
    // This is just to make current text field
    // empty when editing, you can ignore
    func textFieldDidBeginEditing(_ textField: UITextField)
    {
        textField.text = ""
    }
    
    // Reset when text field is no longer being edited
    func textFieldDidEndEditing(_ textField: UITextField)
    {
        activeTextFieldIndexPath = nil
        tableView.reloadData()
    }
}

6. TableView Delegate

extension InputViewController: UITableViewDelegate
{
    func scrollViewDidScroll(_ scrollView: UIScrollView)
    {
        // End editing when scrolling table view
        // This is for me, you can have another way
        view.endEditing(true)
    }

    func tableView(_ tableView: UITableView,
                   heightForRowAt indexPath: IndexPath) -> CGFloat
    {
        // Return a random height
        return 80
    }
}

End result is something like this:

Update all multiple text fields in UITableView Cell when typing UITableViewCell UITextFieldDelegate

Hope this gives you enough to set you on your way.

1
Mahyar Zhiani On

inside your textFieldDidBeginEditing and textFieldDidChangeSelection delegates, call tableView.reloadData()