skip to Main Content

I have a TableView containing an Array of currencies. The array can have multiple elements, and TableView cell contains a TextField.

enter image description here

Users may or may not add prices for all the currencies. I have implemented textFieldDidEndEditing delegate method to get textField’s value. This is my CellForRowAt:


struct PriceModel {
    var country : CountryListData?
    var price : String?
    var cancel : Bool?
    
    init(country: CountryListData? = nil, price: String? = nil, cancel: Bool? = nil) {
        self.country = country
        self.price = price
        self.cancel = cancel
    }
}


// MARK: - Variables
    var priceArray = [PriceModel]()
    var countryArray = [CountryListData]()
    var onSaveClick : (([PriceModel])->Void)?
    var textFieldValues: [IndexPath: String] = [:]



func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "PriceTableViewCell", for: indexPath) as! PriceTableViewCell
        let obj = self.countryArray[indexPath.row]
        cell.editPriceCodeLabel.text = obj.iso_code ?? ""
        cell.editPriceTextField.tag = indexPath.row
        cell.editPriceTextField.delegate = self
        if let value = self.textFieldValues[indexPath] {
            cell.editPriceTextField.text = value
            print("value --> (value) at index --> (indexPath.row)")
            let price = PriceModel(country: obj, price: value, cancel: true)
            print("price --> (price)")
            self.priceArray.append(price)
            print("priceArray --> (self.priceArray)")
        }
        
        cell.selectionStyle = .none
        return cell
    }

// MARK: - TextField Delegate
extension PriceScreen: UITextFieldDelegate {
    func textFieldDidEndEditing(_ textField: UITextField) {
        let indexPath = IndexPath(row: textField.tag, section: 0)
        self.textFieldValues[indexPath] = textField.text ?? ""
        print("self.textFieldValues --> (self.textFieldValues)")
        self.retailPriceTableView.reloadRows(at: [indexPath], with: .automatic)
    }
}

I have a problem with my code. I noticed that if I do not dismiss the keyboard, the value from the text field does not get added to the self.textFieldValues[indexPath] variable. I am not sure if I am using the correct method to get the values. Can someone please help me or provide guidance? Any type of assistance would be greatly appreciated.

Thank You!

2

Answers


  1. Why do you need IndexPath when you have textField.tag, So simply do the following in textFieldDidEndEditing method:

    func textFieldDidEndEditing(_ textField: UITextField) {
            self.textFieldValues[textField.tag] = textField.text ?? ""
            print("self.textFieldValues --> (self.textFieldValues)")
            self.retailPriceTableView.reload
        }
    

    And replace the following code in cellForRowAt method:

    if let value = self.textFieldValues[indexPath.row] {
                cell.editPriceTextField.text = value
                print("value --> (value) at index --> (indexPath.row)")
                let price = PriceModel(country: obj, price: value, cancel: true)
                print("price --> (price)")
                self.priceArray.append(price)
                print("priceArray --> (self.priceArray)")
            }
    
    Login or Signup to reply.
  2. Whether assigning the text field’s .delegate or, using a more "Swifty" approach with a closure, you want to update your data as the field is being edited, rather than on textFieldDidEndEditing.

    So, in your PriceTableViewCell we’ll declare a closure:

    var priceClosure: ((PriceTableViewCell, String) -> ())?
    

    then, we’ll add a target action to the text field:

    textField.addTarget(self, action: #selector(didEdit(_:)), for: .editingChanged)
    

    and, this selector/func:

    @objc func didEdit(_ sender: UITextField) {
        // inform the controller that the text field text was edited
        priceClosure?(self, textField.text ?? "")
    }
    

    In your controller, in cellForRowAt, we’ll assign the closure:

    // add the closure
    c.priceClosure = { [weak self] aCell, aStr in
        guard let self = self,
              let idx = self.tableView.indexPath(for: aCell)
        else { return }
        self.myData[idx.row].price = aStr
        self.updateTotal()
    }
    

    That closure will be called as the field is being edited … no waiting for the user to tap into a different field or manually dismiss the keyboard.

    Here is a quick complete example…


    Simple label & text field cell class – with the closure:

    class PriceTableViewCell: UITableViewCell {
        
        var priceClosure: ((PriceTableViewCell, String) -> ())?
        
        let label = UILabel()
        let textField = UITextField()
        
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        private func commonInit() {
            [label, textField].forEach { v in
                v.translatesAutoresizingMaskIntoConstraints = false
                contentView.addSubview(v)
            }
            let g = contentView.layoutMarginsGuide
            NSLayoutConstraint.activate([
                
                label.leadingAnchor.constraint(equalTo: g.leadingAnchor),
                label.centerYAnchor.constraint(equalTo: g.centerYAnchor),
                
                textField.topAnchor.constraint(equalTo: g.topAnchor),
                textField.trailingAnchor.constraint(equalTo: g.trailingAnchor),
                textField.bottomAnchor.constraint(equalTo: g.bottomAnchor),
                textField.widthAnchor.constraint(equalToConstant: 120.0),
                
            ])
            
            textField.borderStyle = .roundedRect
            textField.keyboardType = .decimalPad
            textField.addTarget(self, action: #selector(didEdit(_:)), for: .editingChanged)
        }
        @objc func didEdit(_ sender: UITextField) {
            // inform the controller that the text field text was edited
            priceClosure?(self, textField.text ?? "")
        }
        
    }
    

    Simplified PriceModel:

    struct PriceModel {
        var name: String = ""
        var price: String = ""
    }
    

    Example controller class – with a "running total" label at the top:

    class PriceScreenVC: UIViewController {
        
        var myData: [PriceModel] = [
            PriceModel(name: "A", price: ""),
            PriceModel(name: "B", price: ""),
            PriceModel(name: "C", price: ""),
            PriceModel(name: "D", price: ""),
        ]
        
        let runningTotalLabel = UILabel()
        let tableView = UITableView()
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            [runningTotalLabel, tableView].forEach { v in
                v.translatesAutoresizingMaskIntoConstraints = false
                view.addSubview(v)
            }
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                
                runningTotalLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
                runningTotalLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
                runningTotalLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
                
                tableView.topAnchor.constraint(equalTo: runningTotalLabel.bottomAnchor, constant: 8.0),
                tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
                tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
                tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
                
            ])
            
            tableView.register(PriceTableViewCell.self, forCellReuseIdentifier: "priceCell")
            tableView.dataSource = self
            tableView.delegate = self
            
            runningTotalLabel.textAlignment = .center
            runningTotalLabel.text = "Total: 0"
        }
    
        func updateTotal() {
            var t: Double = 0
            myData.forEach { p in
                if let val = Double(p.price) {
                    t += val
                }
            }
            runningTotalLabel.text = "Total: (t)"
        }
    }
    
    
    extension PriceScreenVC: UITableViewDataSource, UITableViewDelegate {
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return myData.count
        }
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let c = tableView.dequeueReusableCell(withIdentifier: "priceCell", for: indexPath) as! PriceTableViewCell
            
            c.label.text = myData[indexPath.row].name
            c.textField.text = myData[indexPath.row].price
    
            // add the closure
            c.priceClosure = { [weak self] aCell, aStr in
                guard let self = self,
                      let idx = self.tableView.indexPath(for: aCell)
                else { return }
                self.myData[idx.row].price = aStr
                self.updateTotal()
            }
            
            return c
        }
        
    }
    

    Looks like this when running:

    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search