You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift UITableView单元格数值求和异常:切换单元格后累计值重置

问题:UITableView单元格切换时总价累计重置为0

我做了一个咖啡馆菜单样式的UITableView,支持添加食物并计算总价。现在需要把所有单元格里sumLabel的数值求和显示在底部,但遇到问题:只能统计当前单元格的数值,切换到其他单元格点“+”时,累计值直接从0开始。

我已经做的步骤:

  • 在MainViewController里创建了空数组totalArr: [Int] = []
  • 在CustomCafeTableViewCell里实例化了MainViewController
  • 在changeSum()方法里把价格追加到totalArr,问题就出在这。

相关代码

// TableViewController here 
protocol CustomCellDelegate {
    func decreaseNumber(cell: CustomCafeTableViewCell, number: Int)
    func decreaseTotal(cell: CustomCafeTableViewCell, number: Int)
}

class MainViewController: UITableViewController {
    let minValue = 0
    var totalArr: [Int] = []
    
    let sumCount: UILabel = {
        let foodCount = UILabel(frame: CGRect(x: 0, y: 0, width: 250, height: 50))
        foodCount.textColor = .black
        foodCount.textAlignment = .center
        foodCount.translatesAutoresizingMaskIntoConstraints = false
        return foodCount
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let customView = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 50))
        customView.backgroundColor = UIColor.systemBackground
        sumCount.text = "Sum: \(totalArr.reduce(0, +))"
        customView.addSubview(sumCount)
        tableView.register(CustomCafeTableViewCell.self, forCellReuseIdentifier: CustomCafeTableViewCell.identifier)
        tableView.tableFooterView = customView
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 3
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if section == 0 {
            return "Salads"
        } else if section == 1 {
            return "Drinks"
        } else {
            return "Desserts"
        }
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 3
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: CustomCafeTableViewCell.identifier, for: indexPath)
        cell.selectionStyle = .none
        return cell
    }
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }
    override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        if section == 2 {
            return 50
        }
        return 0
    }
}
// TableViewCell here
class CustomCafeTableViewCell: UITableViewCell {
    var vc = MainViewController()
    var foodPrice = 10
    var totalSum = 0
    
    static let identifier = "cell"
    private var delegate: CustomCellDelegate?
    let minValue = 0

    @objc func decreaseFunc() {
        changeQuantity(by: -1)
        changeSum(by: -foodPrice)
    }
    @objc func increaseFunc() {
        changeQuantity(by: 1)
        changeSum(by: foodPrice)
    }

    func changeQuantity(by amount: Int) {
        var quality = Int(foodCount.text!)!
        quality += amount
        if quality < minValue {
            quality = 0
            foodCount.text = "0"
        } else {
            foodCount.text = "\(quality)"
        }
        delegate?.decreaseNumber(cell: self, number: quality)
    }

    func changeSum(by amount: Int) {
        var sum = Int(sumLabel.text!)!
        sum += amount
        if sum < minValue {
            sum = 0
            sumLabel.text = "0"
        } else {
            sumLabel.text = "\(sum)"
            vc.totalArr.append(amount)
            print("sum to show \(vc.totalArr.reduce(0, +))")
        }
        delegate?.decreaseTotal(cell: self, number: sum)
    }

    private let foodLabel: UILabel = {
        let label = UILabel()
        label.textColor = .black
        label.text = "food"
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    private let priceLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    private let increaseButton: UIButton = {
        let button = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
        button.setImage(UIImage(systemName: "plus.rectangle"), for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private let decreaseButton: UIButton = {
        let button = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
        button.setImage(UIImage(systemName: "minus.rectangle"), for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private let foodCount: UILabel = {
        let foodCount = UILabel()
        foodCount.text = "0"
        foodCount.translatesAutoresizingMaskIntoConstraints = false
        return foodCount
    }()

    let sumLabel: UILabel = {
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "0"
        return label
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        contentView.addSubview(foodLabel)
        contentView.addSubview(priceLabel)
        contentView.addSubview(increaseButton)
        contentView.addSubview(foodCount)
        contentView.addSubview(decreaseButton)
        contentView.addSubview(sumLabel)
        
        priceLabel.text = "\(foodPrice) $"
        increaseButton.addTarget(self, action: #selector(increaseFunc), for: .touchUpInside)
        decreaseButton.addTarget(self, action: #selector(decreaseFunc), for: .touchUpInside)

        foodLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 5).isActive = true
        priceLabel.leadingAnchor.constraint(equalTo: foodLabel.leadingAnchor, constant: 75).isActive = true
        decreaseButton.leadingAnchor.constraint(equalTo: priceLabel.leadingAnchor, constant: 170).isActive = true
        foodCount.leadingAnchor.constraint(equalTo: decreaseButton.leadingAnchor, constant: 35).isActive = true
        increaseButton.leadingAnchor.constraint(equalTo: foodCount.leadingAnchor, constant: 20).isActive = true
        sumLabel.leadingAnchor.constraint(equalTo: increaseButton.leadingAnchor, constant: 35).isActive = true
    }

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

核心问题

你在CustomCafeTableViewCell里写的var vc = MainViewController()是新建了一个全新的MainViewController实例,不是当前屏幕上显示的那个!每个单元格都会创建自己的vc对象,它们的totalArr都是独立的空数组,所以切换单元格时,累计的是新实例里的数组,自然从0开始。

另外还有几个隐性问题:

  1. 定义了CustomCellDelegate但没给单元格设置代理,代理方法也没实现
  2. 没有维护数据源记录每个菜品的数量/小计,UITableView单元格是复用的,滚动后之前的数值会丢失
  3. sumCount标签只会在viewDidLoad时初始化一次,之后数值变化不会更新

修复方案

1. 定义数据模型,持久化菜品状态

先创建Food模型类,用来记录每个菜品的名称、单价、数量、小计,避免单元格复用导致数据丢失:

class Food {
    let name: String
    let price: Int
    var quantity: Int = 0
    var subtotal: Int { return price * quantity }
    
    init(name: String, price: Int) {
        self.name = name
        self.price = price
    }
}

2. 重构MainViewController,维护数据源并实现代理

  • 用二维数组作为数据源,对应三个分类
  • 实现CustomCellDelegate方法,更新数据源并计算总价
  • 给单元格设置代理,传递当前的Food模型
  • 实时更新底部的sumCount标签

3. 修改CustomCafeTableViewCell,移除多余的VC实例

  • 删除var vc = MainViewController()
  • 添加Food属性,接收外部传递的模型
  • 点击按钮时更新模型的数量,通过代理通知ViewController刷新数据和总价

修复后完整代码

MainViewController

protocol CustomCellDelegate: AnyObject {
    func foodQuantityChanged(for cell: CustomCafeTableViewCell)
}

class MainViewController: UITableViewController, CustomCellDelegate {
    // 数据源:三个分类,每个分类下的菜品
    var sections: [[Food]] = [
        [Food(name: "凯撒沙拉", price: 25), Food(name: "田园沙拉", price: 20), Food(name: "鸡肉沙拉", price: 30)],
        [Food(name: "美式咖啡", price: 15), Food(name: "橙汁", price: 12), Food(name: "奶茶", price: 18)],
        [Food(name: "芝士蛋糕", price: 35), Food(name: "提拉米苏", price: 40), Food(name: "冰淇淋", price: 22)]
    ]
    
    let sumCount: UILabel = {
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: 250, height: 50))
        label.textColor = .black
        label.textAlignment = .center
        label.font = UIFont.systemFont(ofSize: 16, weight: .medium)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupFooterView()
        tableView.register(CustomCafeTableViewCell.self, forCellReuseIdentifier: CustomCafeTableViewCell.identifier)
        updateTotalSum()
    }
    
    private func setupFooterView() {
        let customView = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 50))
        customView.backgroundColor = .systemBackground
        customView.addSubview(sumCount)
        NSLayoutConstraint.activate([
            sumCount.centerXAnchor.constraint(equalTo: customView.centerXAnchor),
            sumCount.centerYAnchor.constraint(equalTo: customView.centerYAnchor)
        ])
        tableView.tableFooterView = customView
    }
    
    // 计算所有菜品的总价并更新标签
    private func updateTotalSum() {
        let total = sections.flatMap { $0 }.reduce(0) { $0 + $1.subtotal }
        sumCount.text = "总价: \(total) $"
    }
    
    // MARK: - UITableViewDataSource & Delegate
    override func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }
    
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        let titles = ["沙拉", "饮品", "甜点"]
        return titles[section]
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sections[section].count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: CustomCafeTableViewCell.identifier, for: indexPath) as! CustomCafeTableViewCell
        cell.selectionStyle = .none
        let food = sections[indexPath.section][indexPath.row]
        cell.configure(with: food)
        cell.delegate = self
        return cell
    }
    
    // MARK: - CustomCellDelegate
    func foodQuantityChanged(for cell: CustomCafeTableViewCell) {
        guard let indexPath = tableView.indexPath(for: cell) else { return }
        let food = sections[indexPath.section][indexPath.row]
        cell.sumLabel.text = "\(food.subtotal)"
        updateTotalSum()
    }
}

CustomCafeTableViewCell

class CustomCafeTableViewCell: UITableViewCell {
    weak var delegate: CustomCellDelegate?
    var currentFood: Food?
    
    static let identifier = "cell"
    private let minValue = 0

    @objc func decreaseFunc() {
        guard let food = currentFood else { return }
        if food.quantity > minValue {
            food.quantity -= 1
            updateCellUI()
            delegate?.foodQuantityChanged(for: self)
        }
    }
    
    @objc func increaseFunc() {
        currentFood?.quantity += 1
        updateCellUI()
        delegate?.foodQuantityChanged(for: self)
    }
    
    // 用Food模型配置单元格UI
    func configure(with food: Food) {
        currentFood = food
        foodLabel.text = food.name
        priceLabel.text = "\(food.price) $"
        foodCount.text = "\(food.quantity)"
        sumLabel.text = "\(food.subtotal)"
    }
    
    // 更新单元格的数量和小计标签
    private func updateCellUI() {
        guard let food = currentFood else { return }
        foodCount.text = "\(food.quantity)"
        sumLabel.text = "\(food.subtotal)"
    }

    // MARK: - UI Components
    private let foodLabel: UILabel = {
        let label = UILabel()
        label.textColor = .black
        label.font = UIFont.systemFont(ofSize: 16)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    private let priceLabel: UILabel = {
        let label = UILabel()
        label.textColor = .gray
        label.font = UIFont.systemFont(ofSize: 14)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    private let increaseButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "plus.rectangle"), for: .normal)
        button.tintColor = .systemBlue
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private let decreaseButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "minus.rectangle"), for: .normal)
        button.tintColor = .systemBlue
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private let foodCount: UILabel = {
        let label = UILabel()
        label.text = "0"
        label.textAlignment = .center
        label.font = UIFont.systemFont(ofSize: 16)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

    let sumLabel: UILabel = {
        let label = UILabel()
        label.text = "0"
        label.textAlignment = .right
        label.font = UIFont.systemFont(ofSize: 16, weight: .medium)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    }()

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

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setupUI() {
        contentView.addSubview(foodLabel)
        contentView.addSubview(priceLabel)
        contentView.addSubview(increaseButton)
        contentView.addSubview(foodCount)
        contentView.addSubview(decreaseButton)
        contentView.addSubview(sumLabel)
        
        increaseButton.addTarget(self, action: #selector(increaseFunc), for: .touchUpInside)
        decreaseButton.addTarget(self, action: #selector(decreaseFunc),
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 01:01:23