简体   繁体   中英

summing values on UITableView Cells swift

I have a TableView in a view controller and a UILabel in the UIViewController too. Everything gets displayed well but I am trying to achieve something and I cannot simply get the Logic behind it.

The tableview cell has a UILabel that has Int values on the cells, thiese numbers varies now how do I get the sum of the numbers on the Label in the tableView cell and display on the Label in the view controller.

I have tried creating a variable in the UIView controller and adding this value to t but I am unable to do that because I cannot add this number together

any help as to how to do this. Thanks

var total: Double?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        switch indexPath.section {
        case 0:
            let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! BrokageCheckoutCell
            cell.configure()
            total = Double("\(cell.subPrice.text!)")
            cell.selectionStyle = .none
            return cell
        case 1:
            let cell = tableView.dequeueReusableCell(withIdentifier: cellId2, for: indexPath) as! OtherCheckoutCell
            cell.configure()
            cell.selectionStyle = .none
            return cell
        default: break
        }
        return UITableViewCell()
    }

You ask:

how do I get the sum of the numbers on the Label in the tableView cell and display on the Label in the view controller

In short, you don't. The label in the table view cell (which is part of the “view”) is not our source of data. It's only used to show the individual strings.

For example, let's say your app has 100 items to be summed, but the app's table view shows, say, only 12 rows at a time. As a memory and performance improvement, iOS will reuse cells that scroll out of view in order to show the contents for new rows that scroll into view. But this means that you can't say “sum all of the contents of the labels in those 100 cells” because there aren't 100 cells; there are only 12.

Your app should instead refer to its “model”, that structure (eg, an array) that cellForRowAt used to determine what to show in a given table view cell. So, if you want to add up a grand total, you should add up the values in that array, not referring to the cells at all.

And if your cell has controls that allow data to be modified, then the cell should initiate the update of the model. Then, the process of calculating the grand total (summing the values in the array) still works.

So, let's consider an example:

  1. You should have a model that contains the numbers that you want to sum. Your example is a little unclear, so I'll create an example where each table row contained a name, a unit price, and a quantity:

     struct Item { let name: String let unitPrice: Decimal var quantity: Decimal } 

    In this example, the total for any given row will be the quantity times the unit price. And the grand total for all the rows will be the sum of all of those products.

  2. Then your view controller might have an array of those for its model:

     var items: [Item] = ... 
  3. Then when you want to update your grand total label, you'd just have a method that calculated the quantity times the price for the total for each row and then sum up those values to calculate the grand total. It will then update the grand total text field accordingly:

     private extension ViewController { func updateTotal() { let total = items .map { $0.quantity * $0.unitPrice } .reduce(0, +) totalLabel.text = totalFormatter.string(for: total) } } 

    Note, I am not retrieving these numbers from the cells (because those might be reused), but rather I'm retrieving all the data I need from the model.

  4. The key here, though, is if your cell, for example, allowed the user to change one of those values, you need a mechanism to update the table view controller's model. We'll use a protocol for that:

     protocol ItemCellDelegate: class { func cell(_ cell: ItemCell, didUpdateQuantity quantity: Decimal) } class ItemCell: UITableViewCell { @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var quantityTextField: UITextField! @IBOutlet weak var unitPriceLabel: UILabel! static let quantityFormatter: NumberFormatter = ... static let priceFormatter: NumberFormatter = ... weak var delegate: ItemCellDelegate? } 

    Obviously, when you configure the cell, the view controller will specify the delegate for updates to the text field (or whatever):

     // MARK: Public methods extension ItemCell { func configure(name: String, quantity: Decimal, unitPrice: Decimal, delegate: ItemCellDelegate) { self.delegate = delegate nameLabel.text = name quantityTextField.text = ItemCell.quantityFormatter.string(for: quantity) unitPriceLabel.text = ItemCell.priceFormatter.string(for: unitPrice) } } 

    And when the ItemCell gets updates, it just calls the delegate:

     // MARK: Private methods private extension ItemCell { @IBAction func didUpdateQuantity(_ sender: UITextField) { var quantity: Decimal? if let text = sender.text, let value = ItemCell.quantityFormatter.number(from: text) { quantity = value.decimalValue } delegate?.cell(self, didUpdateQuantity: quantity ?? 0) } } 

    Then, when the view controller configured the cell, it would supply itself as the delegate:

     extension ViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell let item = items[indexPath.row] cell.configure(name: item.name, quantity: item.quantity, unitPrice: item.unitPrice, delegate: self) return cell } } 

    And, of course, the view controller would conform to that protocol to accept updates to the text field and update the model:

     extension ViewController: ItemCellDelegate { func cell(_ cell: ItemCell, didUpdateQuantity quantity: Decimal) { guard let indexPath = tableView.indexPath(for: cell) else { return } items[indexPath.row].quantity = quantity updateTotal() } } 

    And, as you can see, if can, after updating the model, it can automatically update the total, too.

The key here is that we never use the cells as a place where we hold data. The cells are just for (a) displaying data and (b) informing the view controller if there are any updates it needs to be aware of.

We strive for a very clear separation of responsibilities, where “views” are for showing and interacting with controls presently on screen and the “model” contains all of our data.

The usual way to do this is to have a delegate of the cell, and make your ViewController this delegate. Whenever the value of the cell changes, send this value through the delegate so that the view controller can add up all these values.

That's the quick way.

The better way to do this is to have a datasource for your cells that keeps track of the values. This is better, because as the cells scroll on and off the screen, the datasource can keep track of the values and restore them when that index path becomes visible again.

Since the data source knows what the values are, it's trivial to sum them.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM