swiftuikituitextfieldtextfielduitextfielddelegate

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


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
}

Solution

  • 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.