Skip to content

Instantly share code, notes, and snippets.

@shawn-frank
Created February 16, 2022 17:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shawn-frank/09339aea4ec8f5321c8877e97b585c22 to your computer and use it in GitHub Desktop.
Save shawn-frank/09339aea4ec8f5321c8877e97b585c22 to your computer and use it in GitHub Desktop.
A UITableView with editable UITextFields in UITableView cells that change based on the value of another
//
// InputViewController.swift
// TestApp
//
// Created by Shawn Frank on 30/01/2022.
//
import UIKit
// 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!
}
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
}
}
class InputViewController: UIViewController
{
let tableView = UITableView()
// Initialize an array of 12 finance models
var financeModels =
Array<FinanceModel>(repeating: FinanceModel(number: 0), count: 12)
// This will store current editing cell which is active
var activeTextFieldIndexPath: IndexPath?
// Array to store last interacted textfields
var inputQueue: [IndexPath] = []
// Max capacity of input queue
let inputQueueCapacity = 2
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
}
}
extension InputViewController: UITableViewDataSource
{
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int
{
// Random number for me
return financeModels.count
}
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
cell.selectionStyle = .none
// 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 = financeModels[indexPath.row].number
{
// Set text of number in model * row number
// Do any calculation you like, this is just an
// example
cell.textField.text = "\(number)"
}
else
{
// If no valid number, set blank
cell.textField.text = ""
}
}
}
extension InputViewController: UITextFieldDelegate
{
// Respond to new text in the text field
func textFieldDidChangeSelection(_ textField: UITextField)
{
// Get the coordinates of where we tapped in the table
let tapLocation = textField.convert(textField.bounds.origin,
to: tableView)
// 1. Convert generic UITextField to MappedTextField
// 2. && Retrieve index path from where we tapped
// 3. && Retrieve text from the text field
// 4. && Check if text is valid number
// 5. && Only make changes when text field changes
if let textField = textField as? MappedTextField,
let indexPath = self.tableView.indexPathForRow(at: tapLocation),
let text = textField.text,
let number = Int(text),
number != financeModels[indexPath.row].number
{
// Assign local variable with index path we are editing
activeTextFieldIndexPath = indexPath
// dummy calculations
if number > 0
{
// Update all calculations in finance model
for (index, _) in financeModels.enumerated()
{
// update current value in text field
if index == indexPath.row
{
financeModels[index].number = number
continue
}
// Random calculations for other cells
// Use your own calculation
financeModels[index].number = number * index
}
}
// 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) {
print("done")
}
}
}
func textFieldDidBeginEditing(_ textField: UITextField)
{
// Get the coordinates of where we tapped in the table
let tapLocation = textField.convert(textField.bounds.origin,
to: tableView)
// 1. Get the of the tapped text field
// 2. Make sure this index path is not being tracked
if let indexPath = self.tableView.indexPathForRow(at: tapLocation),
!inputQueue.contains(indexPath)
{
// You can make it blank
textField.text = ""
}
// The text field is being tracked so do nothing
}
func textFieldDidEndEditing(_ textField: UITextField)
{
// Get the coordinates of where we tapped in the table
let tapLocation = textField.convert(textField.bounds.origin,
to: tableView)
// 1. Convert UITextField to MappedTextField (my custom class you can ignore)
// 2. && Retrieve index path from where we tapped
// 3. && Retrieve text from the text field
// 4. && Check if text is valid number
if let textField = textField as? MappedTextField,
let indexPath = self.tableView.indexPathForRow(at: tapLocation),
let text = textField.text,
let number = Int(text)
{
// Check input queue does not contain index path
// && that user has entered some valid number
if !inputQueue.contains(indexPath)
&& number > 0
{
// Check if you reached max capacity
if inputQueue.count >= inputQueueCapacity
{
// Dequeue from the front of the queue
inputQueue.removeFirst()
}
// Append index path to queue as it is not in queue
inputQueue.append(indexPath)
}
}
// Reset currently active text field
activeTextFieldIndexPath = nil
}
}
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
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment