1

So it appears that a Table View in Swift dynamically reloads data onto the screen based on the scroll position. However, in my case, I don't want the mechanism to work this way since the items in my table view update the total price of the user's order and is unnecessarily screwing up the total price value. When I scroll up or down, the cells in which the reload data is being called on are reperforming the math and is leading to the incorrect total price.

My wish is to recalculate the price using the reloadData() function only when the UIStepper is pressed on a particular cell rather than on scrolling also. I've done this by having an IBAction function that calls reloadData when the stepper is pressed. The problem with the math and internal data occurs when the table view is scrolled on and is repeatedly calling the reloadData function as the visible cells are changed which I don't want it to do for any form of scrolling whatsoever.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let add_on = JSON(self.add_ons[indexPath.row])
        let cell = tableView.dequeueReusableCell(withIdentifier: "AddOnCell", for: indexPath) as! AddOnTableViewCell

        quantities[indexPath.row] = cell.quantity
        //cell.quantityLabel.text = String(quantities[indexPath.row])

        let price = Double(add_on["price"].int!)/100.0
        let quantity:Double = Double(quantityLabel.text!)!

        //when user tries to increase add-on quantity beyond ticket quantity
        if(Int(quantity) < cell.quantity){
            print("QUANTITY UPDATED")
            cell.quantity -= 1
            quantities[indexPath.row] = cell.quantity
        }
        self.addOnPrice = updateTotal(quantities: self.quantities)
        totalPriceLabel.text = (Double(event_subtotal * quantity) + addOnPrice).dollarRound()

        cell.addonLabel?.text = add_on["name"].string! + " (+$\(price.roundTo(places: 2))" + ")"

        return cell
}

func updateTotal(quantities: [Int]) -> Double{
    var result:Double = 0.0
    for i in 0..<quantities.count{
        let curr_addon = JSON(self.add_ons[i])
        let price = Double(curr_addon["price"].int!)/100.0
        result += Double(quantities[i]) * price
    }
    return result.dollarRoundDouble()
}

@IBAction func stepperClicked(_ sender: UIStepper) {
    self.addOnPrice = 0.0
    AddOnTableView.reloadData()
}

Below is the cell data model:

class AddOnTableViewCell: UITableViewCell{

    @IBOutlet weak var addonLabel: UILabel!
    @IBOutlet weak var quantityLabel: UILabel!
    @IBOutlet weak var quantityStepper: UIStepper!
    var quantity : Int  = 0 {
        didSet{
            self.quantityLabel.text = String(quantity)
            self.quantityStepper.value = Double(quantity)
        }
    }

    //ref: https://stackoverflow.com/questions/42876739/swift-increment-label-with-stepper-in-tableview-cell
    @IBAction func quantityStep(_ sender: UIStepper) {
        self.quantity = Int(sender.value)
        self.quantityLabel.text = String(quantity)
    } 
}

Behavior: Upon scrolling, the total price calculated is changing without stepper interaction. Based on scroll position, the quantity for 1 item is "exchanging" the quantity value with another item. First item quantity becoming 1 updates last item's quantity for some reason. Unable to debug this behavior.

UI for reference enter image description here

btrballin
  • 1,420
  • 3
  • 25
  • 41
  • This sounds like a problem to do with cell reuse. Can you share your table view delegate code? – Chris May 04 '19 at 17:14
  • 1
    Thanks for the reply. I went ahead and updated the question with the UI screenshot as well as the source code. If any part doesn't make sense, let me know and I can clarify. – btrballin May 04 '19 at 20:28
  • Which variable holds the total price? If the interface will always look like this, it might be easier with static cells (i.e. using IB and not creating cells in code). Also, it might be better to not have data model stuff (quantity) as a property of a UI element (the cells). The data model should be invisible behind the scenes and the data taken from it to display in cells. I’m thinking of a way to solve this. – Chris May 04 '19 at 20:57
  • `totalPriceLabel` is the label holding the total price. `self.addOnPrice` is what is added on top of the subtotal to calculate the total price – btrballin May 05 '19 at 00:20

1 Answers1

2

In UIKit, cells are being reused as you scroll the list. Assume that the cell values just come and go. It should not hold long term values. Calculating of the total should not be in cellForRow method. First of all, you are missing a data model to hold all the data. The way of doing it, is that you create a structure that represents an add-on (has quantity, price, etc). When the json is received, you parse all of it and store it in the array. The array is then used as you master source. The cellForRow should use the data there, and pass the structure to the AddOnTableViewCell so that everything is separated nicely. When the user hits the quantity step, it should call a delegate class that you create, which is implemented in the view controller, which updates the array with the updated quantity and then does the total. Assume that the cell values just come and go.

So create a new class:

protocol AddOnUpdated: class {
    func quantityUpdated(label: String, value: Int)
}

Then add a member to your cell {

class AddOnTableViewCell: UITableViewCell{
  weak var delegate: AddOnUpdated?

  func quantityStep {
     .. same code
     delegste?.quantityUpdated(label, value)
  }
}

Then in the view controller, do not update the totals when you create the cell. Just implement the delegate, and set the delegate value when you create the cell to self.

So now, the delegate should be called whenever the value of any cell is updated. At that point, you need to update your values array, and update the total.

Hope all this makes sense.

Yuval Tal
  • 645
  • 7
  • 12
  • If you don't mind, can you complete the source code for quantity updated and calling the delegate with updated values? I'm still new and your implementation isn't very clear – btrballin May 05 '19 at 00:23
  • Can you please support your solution with a more complete source code snippet? I'm trying to resolve this bug soon so that I can move onto other pending tasks for my app – btrballin May 06 '19 at 00:19
  • https://medium.com/@aapierce0/swift-using-protocols-to-add-custom-behavior-to-a-uitableviewcell-2c1f09610aa1 – Yuval Tal May 07 '19 at 23:29
  • https://stackoverflow.com/questions/39480831/swift-custom-uitableviewcell-delegate-to-uiviewcontroller-only-one-protocol-wor – Yuval Tal May 07 '19 at 23:29
  • So let me see if I understood this correctly...(1) create an array of add_on objects (a struct) containing quantity, title, and price based on the data received from JSON. (2) Implement protocol `AddOnUpdated` in my view controller with the `AddOnUpdated` function which updates the quantity of the corresponding add-on in the array we constructed from JSON data and also calculates total c. – btrballin May 08 '19 at 03:28
  • I tried your solution and I see to be going somewhere with it. The bug I see in my list view is when I scroll to the bottom and click on the stepper, it's updating the 0 quantity to 4 if the first add on item's quantity was set to 3 before scrolling to the bottom. I'm using `sender.value` for the increment/decrementing of each cell's quantity as you can see in my code above and for some reason, this value isn't unique to each cell and is being shared oddly only between first and last cells – btrballin May 08 '19 at 20:43