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
}
At a high level:
UITextFieldDelegate
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:
Hope this gives you enough to set you on your way.