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

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

This is the behaviour I want to implement: https://cln.sh/xjw5Od (short video)

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
    }
Answered by Claude31 in 703344022

OK, so if I understand, you want to type a value in a cell and have twice this value in all other cells as soon as editing ends.

 define a dataSource I receive from FRC in my tableView cell as:

  • But you do not define the value.
  • Is it the initial value ? because later it will be replaced by 2*value entered elsewhere

The use of dataSource remains the solution.

Just adapt the code like this (maybe you can manage color directly, but that's better to rely on dataSource probably:

in cellForRowAt:

cell.numberTextField.text = String(values[indexPath.row].val)
cell.numberTextField.textColor = values[indexPath.row].color
cell.textField.tag = indexPath.row + 1000  // I offset by 1000, in case you have other textFields here

In delegate func:

   func textFieldDidBeginEditing(_ textField: UITextField) {
        // You want to clear all textFields. Exact ?
       for pos in 0..<values.count {
         values[pos] = (newValue, .systemBlue)
      }
        tableView.reloadData()      // When editing begins in any cell textField
    }
    
    func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {

       let newValue = Int(textField.text) ?? 0 //  the value we typed

       // update dataSource as needed
       for pos in 0..<values.count {
            if pos == textField.tag - 1000 {       // Don't do it for textField that just ended editing
                values[pos] = (newValue, .black)   // Keep typed value for the one that has been edited
           } else {                                // To all others, multiply by 2
                let doubleValue = newValue * 2        
                values[pos] = (doubleValue, .black)      
           }
        tableView.reloadData()
    }

Calculation should be displayed in a real time

To do so, implement code in another delegate func:

     func textFieldDidChangeSelection(_ textField: UITextField) {

       let fieldBeingEditedIndex = textField.tag - 1000    // tag of textField being edited, minus offset to get pos for dataSource
       let newValue = Int(textField.text) ?? 0 //  the value we are typing

       // update dataSource as needed
       for pos in 0..<values.count {
            if pos == fieldBeingEditedIndex {      // Don't do it for textField that is being edited
                values[pos] = (newValue, .black)   // Keep typed value for the one being edited - other color?
           } else {                                // To all others, multiply by 2
                let doubleValue = newValue * 2        
                values[pos] = (doubleValue, .black)      
           }

        var allOtherIndexPath = [IndexPath]()    // All, except the one being edited
        for i in 0..<values.count where i != fieldBeingEditedIndex  {
           allOtherIndexPath.append(IndexPath(row: i, section: 0))
        }
        // reload other cells only, to avoid flicker or other side effects
        tableView.reloadData(at indexPaths: allOtherIndexPath, with animation: .none)
    }

instead of in

    func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {

Is it what you are looking for ?

If not, please explain in a simple use case what you want :

  • what are the initial values in cells (from FRC ?)
  • You type something in a cell
  • What do you then get in all cells ?

I don't understand this piece of code:

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

What is cell ?

Here, you should dequeue a cell and define its content (including textField delegate), using dataSource notably for the content of textField :

Then, when you change in one cell,

  • in textFieldDidEndEditing
  • update the values in dataSource,
  • ask to reload() tableView,
  • every cell would be updated.

Also, why do you reset to "0" when editing ends ?

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

Hi Claude, thank you for the answer!

What is cell ?

Here is the full code for my cell. tableView shows items from NSFetchedResultsController based on predicate "isForConverter" = YES, which I set in another VC.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "converterCell", for: indexPath) as! ConverterTableViewCell
        let currency = fetchedResultsController.object(at: indexPath)
 
        cell.shortName.text = currency.shortName
        cell.fullName.text = currency.fullName
        cell.numberTextField.delegate = self
        
        return cell
    }

Then, when you change in one cell,

I tried to create a global variable, then in textFieldDidEndEditing() assign that variable to a string from textField.text, then assign that variable in cellForRowAt() as cell.numberTextField.text = number and reload a tableView. As a result a received all cells display number just when tableView first loads and I can't edit any number.

Also, why do you reset to "0" when editing ends ?

Base textField.text string for my cell is 0, so when I click on cell I should be able to reset it to "", edit number and if I want to switch to edit another cell, I want that first cell to be 0 again. This is why I set it to 0. Take a look at the video: https://cln.sh/O3wGJE

What I want:

  • I need to be able to edit any cell's textField, type a number and all other cells as soon as I type the number, change their textField.text accordingly after calculation (for example the number I type in edited cell should be multiplied by 2 and displayed in all other cells. And I should be able to pick another cell, type number etc.
  • take a look at the video: https://cln.sh/xjw5Od. Here if I pick any available cell, type number, then all other cells recalculate it's value based on what I typed in editable cell like live.

You do not set the content of cell.numberTextField in cellForRowAt. Why ? You should:

  • create a dataSource values : [val: Int, color: UIColor] where you store the value for each cell (0 to start with) as well as its color ; this array should be initialised with the adequate number of items (numberOfRowsInSection should be equal to number of items in this array).
  • use this dataSource to populate textFields in cellForRowAt:
cell.numberTextField.text = String(values[indexPath.row].val)
cell.numberTextField.textColor = values[indexPath.row].color

I need to be able to edit any cell's textField, type a number and all other cells as soon as I type the number, change their textField.text accordingly after calculation (for example the number I type in edited cell should be multiplied by 2 and displayed in all other cells. And I should be able to pick another cell, type number etc.

I don't understand.

Imagine I have 1, 2, 3 in 3 cells. Do you mean that when typing 5 in cell 3, cell 0 should become 2, cell 2 become 4 and cell 3 : 5 ? 8 ? 10 ? Then you type 20 in cell 0, what should be the result ?

In addition in the delegate func

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

you do not segregate between different cells. Why ?

What I would do:

  • set a tag for each cell textField (in cellForRowAt):
cell.textField.tag = indexPath.row + 1000  // I offset by 1000, in case you have other textFields here
  • Use in delegate func
    func textFieldDidBeginEditing(_ textField: UITextField) {
        // This will only apply to the text being edited
        textField.text = ""
        textField.textColor = UIColor.systemBlue
    }
    
    func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {

       // update dataSource as needed
       for pos in 0..<values.count {
            if pos == textField.tag - 1000 {  // Don't do it for textField that just ended editing
                let newValue = Int(textField.text) ?? 0
                values[pos] = (newValue, .black)       // Just for the one which has been edited
           } else {                                                         // This will apply to all other textFields
                let newValue = values[pos].val * 2     // for instance. Or may depend on the value you just typed in textField with tag textField.tag - 1000 ?
                values[pos] = (newValue, .black)      
           }
        tableView.reloadData()
    }

Hope that I understood your spec correctly.

Thank you very much for your time spent on my question! Appreciate!

You do not set the content of cell.numberTextField in cellForRowAt. Why ?

This is because I want the content to be set by func which multiplies by 2 any number that was typed in currently active cell. And I don't know how I can separate active cell from all others each time I click on a new cell...

Imagine I have 1, 2, 3 in 3 cells. Do you mean that when typing 5 in cell 3, cell 0 should become 2, cell 2 become 4 and cell 3 : 5 ? 8 ? 10 ? Then you type 20 in cell 0, what should be the result ?

When typing 5 in cell 3 I want to receive 5x2 = 10 as an output in any other cells (cell 1 and cell 2) which is currently on the screen. Perform simple calculation (any number typed from active cell, multiple it by 2 and put the result in all other cells which is not active in the tableView as soon as I typed that number. Calculation should be displayed in a real time, not when I typed number and clicked done or closed the keyboard on active cell.

you do not segregate between different cells. Why ?

In this methods I just put some basic code and it should define behaviour for:

        textField.text = ""
        textField.textColor = UIColor.systemBlue

as I click on cell, it becomes active, textField clears itself (by default it displays number 0) and color of active cell turns blue to distinguish which of the cell is active now.

            textField.text = "0"
            textField.textColor = UIColor.black

As soon as I click on another cell to make it textField editable I turn the cell I was just editing into default 0 and text for black as for all non-editable cells

What I would do:

What would you do if your tableView was populated with Core Data from NSFetchedResultsController? Should I create from FRC an additional array for dataSource or I can use the one I receive from FRC? I define a dataSource I receive from FRC in my tableView cell as:

 override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "converterCell", for: indexPath) as! ConverterTableViewCell
        let currency = fetchedResultsController.object(at: indexPath)
 
        cell.shortName.text = currency.shortName
        cell.fullName.text = currency.fullName
        cell.numberTextField.delegate = self
        
        return cell
    }
Accepted Answer

OK, so if I understand, you want to type a value in a cell and have twice this value in all other cells as soon as editing ends.

 define a dataSource I receive from FRC in my tableView cell as:

  • But you do not define the value.
  • Is it the initial value ? because later it will be replaced by 2*value entered elsewhere

The use of dataSource remains the solution.

Just adapt the code like this (maybe you can manage color directly, but that's better to rely on dataSource probably:

in cellForRowAt:

cell.numberTextField.text = String(values[indexPath.row].val)
cell.numberTextField.textColor = values[indexPath.row].color
cell.textField.tag = indexPath.row + 1000  // I offset by 1000, in case you have other textFields here

In delegate func:

   func textFieldDidBeginEditing(_ textField: UITextField) {
        // You want to clear all textFields. Exact ?
       for pos in 0..<values.count {
         values[pos] = (newValue, .systemBlue)
      }
        tableView.reloadData()      // When editing begins in any cell textField
    }
    
    func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {

       let newValue = Int(textField.text) ?? 0 //  the value we typed

       // update dataSource as needed
       for pos in 0..<values.count {
            if pos == textField.tag - 1000 {       // Don't do it for textField that just ended editing
                values[pos] = (newValue, .black)   // Keep typed value for the one that has been edited
           } else {                                // To all others, multiply by 2
                let doubleValue = newValue * 2        
                values[pos] = (doubleValue, .black)      
           }
        tableView.reloadData()
    }

Calculation should be displayed in a real time

To do so, implement code in another delegate func:

     func textFieldDidChangeSelection(_ textField: UITextField) {

       let fieldBeingEditedIndex = textField.tag - 1000    // tag of textField being edited, minus offset to get pos for dataSource
       let newValue = Int(textField.text) ?? 0 //  the value we are typing

       // update dataSource as needed
       for pos in 0..<values.count {
            if pos == fieldBeingEditedIndex {      // Don't do it for textField that is being edited
                values[pos] = (newValue, .black)   // Keep typed value for the one being edited - other color?
           } else {                                // To all others, multiply by 2
                let doubleValue = newValue * 2        
                values[pos] = (doubleValue, .black)      
           }

        var allOtherIndexPath = [IndexPath]()    // All, except the one being edited
        for i in 0..<values.count where i != fieldBeingEditedIndex  {
           allOtherIndexPath.append(IndexPath(row: i, section: 0))
        }
        // reload other cells only, to avoid flicker or other side effects
        tableView.reloadData(at indexPaths: allOtherIndexPath, with animation: .none)
    }

instead of in

    func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {

Is it what you are looking for ?

If not, please explain in a simple use case what you want :

  • what are the initial values in cells (from FRC ?)
  • You type something in a cell
  • What do you then get in all cells ?

what are the initial values in cells (from FRC ?)

My FRC has a custom type called Currency. Currency is a CoreData entity which has attributes: shortName ("USD"), fullName ("American Dollar"), isPickedForConverterScreen (Bool, true), currentValue (Double: 75,44), and so on for other currencies.

My tableView shows the Currency object only when isPickedForConverterScreen == true. tableView cell have 2 labels: for displaying shortName and fullName and a textField. Here is how I define it:

        let cell = tableView.dequeueReusableCell(withIdentifier: "converterCell", for: indexPath) as! ConverterTableViewCell
        let currency = fetchedResultsController.object(at: indexPath)
 
        cell.shortName.text = currency.shortName
        cell.fullName.text = currency.fullName
        
        return cell
    }

You type something in a cell

  • let's assume I have 2 cells in my tableView: USD, RUB.
  • I click on USD cell - type number - RUB cell recalculate its textField.text as: number I typed in USD cell x RUB.CurrentValue. For example I typed 2, then 2 x 75,44 = 150,88 to display in RUB cell. Calculation should be performed live, not when I end editing USD cell

What do you then get in all cells ?

The thing is I need to perform a calculation for all cells based on number I typed in active cell x currentValue number stored for each Currency object. And I should be able to pick another cell with currency and calculate all other currencies based on value I typed + their currentValue

So am I understood correct that my dataSource is the array of Currencies which I can receive as:

let array: [Currencies] = fetchedResultsController.fetchedObjects

I don't define it anywhere in my VC for now. Only when defying cell in cellForRow:

let currency = fetchedResultsController.object(at: indexPath)

So, why don't you use currentValue in cellForRowAt ?

What I would in this case:

  • have a func to update values
func updateValues(inputRow: Int, inputValue: Double) {
// inputCellRow is the cell row which contains the field being edited
// inputValue the value presently in this row
// update each other items aRow in values, based on the inputValue and the conversion between currencies
// something like
// let newValue = values[inputRow].val * currency[aRow] / currency[inputRow]
// values[aRow] = newValue
}
  • In textFieldDidChangeSelection
       let fieldBeingEditedIndex = textField.tag - 1000    // tag of textField being edited, minus offset to get pos for dataSource
       let newValue = Int(textField.text) ?? 0 //  the value we are typing
       // update dataSource as needed
       updateValues(inputRow: fieldBeingEditedIndex, inputValue:  newValue)

aRow is in the loop:

for aRow in 0..<values.count {
  let newValue = values[inputRow].val * currency[aRow] / currency[inputRow]
  values[aRow] = newValue
}
How to simultaneously change textField.text in a prototype cells?
 
 
Q