How to convert number in UITextField and pass it to UILabel in UITableViewCell?

Viewed 223

First, I want to build my app like this money converter.

Cell for Row 0 will have a UITextLabel as a subview of UITableViewCell, which gets a number from users.

Cell for Row 2 will have a UILabel as a subview of UITableViewCell, which will shows the calculated number.

Cell for Row 3 will have a UILabel as a subview of UITableViewCell, which will shows the calculated number. But this number would be different from the number at Row 2.

enter image description here

So I made 3 classes, for 'UITextField', 'UILabel', 'UITableViewController'

Here is the 'UITableViewController' class code.

class TableViewController: UITableViewController, UITextFieldDelegate {

    let fruitsComponents: [String] = ["Apple", "Banana", "Grape", "Pear"]

    let cellReuseidentifier = "cell"
    let anotherCellReuseidentifier = "anotherCell"

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(FruitTableViewCell.self, forCellReuseIdentifier: cellReuseidentifier)
        tableView.register(AnotherFruitTableViewCell.self, forCellReuseIdentifier: anotherCellReuseidentifier)  
    }

    override func numberOfSections(in tableView: UITableView) -> Int {      
        return 1
    }

        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {        
        return fruitsComponents.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if indexPath.row == 0 {
            let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
            cell.textLabel?.text = fruitsComponents[indexPath.row]
            return cell
        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: anotherCellReuseidentifier, for: indexPath) as! AnotherFruitTableViewCell
            cell.textLabel?.text = fruitsComponents[indexPath.row]
            return cell
        }
    }
}

I can get a number from UITextField and calculate it in UITextLabel subclass file.

Or, I can do this in cellForRow syntex in UITableViewController class by making UITextLabel instance directly.

But the problem is, I cannot pass the calculated data to UILabel. Because, TableViewCell was made by 'dequeue' method.

  1. I cannot pass the calculated data because the cells which contains UILabel subview were not made.
  2. Even if the cells are made, I cannot pass the data in 'cell' to 'cell', because both 'cell' and 'cell' was made by 'dequeue' method using same 'let cell'.
  3. Of course, I cannot differentiate the cell at Row2 and the cell at Row 3.

Because of this, now, I cannot make a function that converts UILabel layout after receiving the data from UITextField.

How to convert number in UITextField and pass it to UILabel in UITextViewCell?

-------- Updates ---------

'FruitsTableViewCell' subclass code

class FruitTableViewCell: UITableViewCell, UITextFieldDelegate {

    var numberTextField = UITextField()
    let toolBarKeyBoard = UIToolbar()
    let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
    lazy var doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed))
    var result : String!

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        numberTextField.addTarget(self, action: #selector(valueChanged(_:)), for: .valueChanged)
        self.contentView.addSubview(numberTextField)

    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        numberTextField.frame = CGRect(x: 250, y: 7.5, width: 100, height: 30)
        numberTextField.keyboardType = .numberPad
        toolBarKeyBoard.sizeToFit()
        numberTextField.inputAccessoryView = toolBarKeyBoard
        toolBarKeyBoard.setItems([flexibleSpace, doneButton], animated: false)
    }

    @objc func valueChanged(_ textField: UITextField) {
            var dataDict: [String: Double] = [:]
            dataDict["amount"] = Double(numberTextField.text!) ?? 0
            NotificationCenter.default.post(name: Notification.Name(rawValue: "AmountChanged"), object: nil, userInfo: dataDict)
    }    

    @objc func donePressed() {
        numberTextField.resignFirstResponder()

        }
    }

'AnotherFruitTableViewCell' subclass code

class AnotherFruitTableViewCell: UITableViewCell {


    var outputTextLabel = UILabel()


    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        NotificationCenter.default.addObserver(self, selector: #selector(handleNewAmount(_:)), name: Notification.Name("AmountChanged"), object: nil)
        self.contentView.addSubview(outputTextLabel)

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }


    override func layoutSubviews() {
        super.layoutSubviews()

        outputTextLabel.frame = CGRect(x: 250.0, y: 7.5, width: 100.0, height: 30.0)
    }

    @objc func handleNewAmount(_ notification: Notification) {
        guard let userInfo = notification.userInfo, let amount = userInfo["amount"] as? Double else {
            return
        }
        let finalAmount = amount * 0.05
        outputTextLabel.text = String(finalAmount)
    }
}
2 Answers

This is a perfect example for using Notifications. You should add observers to every cell which contains the label. I'm guessing AnotherFruitTableViewCell is the cell which contains the label and FruitTableViewCell is the one that contains text field.

class AnotherFruitTableViewCell: UITableViewCell {

    // Initialization of label, country and other code

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        NotificationCenter.default.addObserver(self, selector: #selector(handleNewAmount(_:)), name: Notification.Name("AmountChanged"), object: nil)
    }

    @objc func handleNewAmount(_ notification: Notification) {
        guard let userInfo = notification.userInfo, let amount = userInfo["amount"] as? Float else {
            return
        }
        let finalAmount = //Some formula
        //Make the necessary changes to amount based on the country
        label.text = finalAmount
    }

}

Note: If you need to calculate the amount for different countries, you would need something in the label cell to identify the country and handle that when you set the amount to the label from the notification.

Now all you need to do is post a Notification from the text field cell when the value of the text field changes.

class FruitTableViewCell: UITableViewCell, UITextFieldDelegate {

    // Initialization of text field and other code

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

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

    }

    @objc func valueChanged(_ textField: UITextField) {
        var amount: [String: Float] = [:]
        amountDict["amount"] = Int(textField.text!) ?? 0
        NotificationCenter.default.post(name: Notification.Name(rawValue: "AmountChanged"), object: nil, userInfo: amountDict)
    }

}

Edit: For done button click. Remove the target and implement the same thing in your button selector.

@objc func donePressed() {
    numberTextField.resignFirstResponder()

    var amount: [String: Float] = [:]
    amountDict["amount"] = Int(numberTextField.text!) ?? 0
    NotificationCenter.default.post(name: Notification.Name(rawValue: "AmountChanged"), object: nil, userInfo: amountDict)
}

Introduce model class(es) to your app, or at the very least some common "model" properties within your view controller (I cringe at saying that, but it's a first step toward understanding and incorporating model data). Have the viewController code that handles each control reference these same model classes, including code to do the computations based on what are in the model classes.

To trigger an update to all the UI in the table, just call self.tableView.reloadData(). Reloading the visible cells will be plenty fast, and the labels will repopulate from the model data.

(Then someday graduate from MVC to MVVM and have your VM class access the models and do the computations, letting your VC simply blindly update the controls per what the VM gives it.)

Related