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

iOS开发:点击UIStepper操作itemArray触发数组越界错误排查

iOS开发数组越界问题排查:UIStepper触发协议方法时访问itemArray报错

问题描述

在GearComponentViewController的tableView中,自定义GearComponentTableViewCell包含UIStepper组件用于控制数量标签,ViewController遵循自定义协议Stepper。当点击Stepper的加减按钮,取消注释itemArray[userindexPath].quantity += 1或itemArray[userindexPath].quantity -= 1时,触发Thread 1: Fatal error: Index out of range错误,但此时itemArray不为空,已有Cell正常显示。

相关代码

自定义协议Stepper

protocol Stepper {
    func stepperWasPressed(didIncrease: Bool, namePassed: String, userindexPath: Int)
}

GearComponentTableViewCell代码

class GearComponentTableViewCell: UITableViewCell {

    var mainVC = GearComponentViewController()
    var tableViewCellPosition: Int! = nil

    // Image
    @IBOutlet weak var itemImage: UIImageView!

    // Name
    @IBOutlet weak var itemName: UILabel!

    // Weight
    @IBOutlet weak var itemWeight1: UILabel!
    @IBOutlet weak var itemWeight2: UILabel!

    // Quanity
    @IBOutlet weak var itemQuanity: UILabel!
    @IBAction func stepperPressed (_ sender: UIStepper!){
        if (sender.value == 1) {
            print("up and item: \(itemName.text!)");
            sender.value = 0
            mainVC.stepperWasPressed(didIncrease: true, namePassed: itemName.text!, userindexPath: tableViewCellPosition)
        } else if (sender.value == -1) {
            print("down and item: \(itemName.text!)");
            sender.value = 0
            mainVC.stepperWasPressed(didIncrease: false, namePassed: itemName.text!, userindexPath: tableViewCellPosition)
        }
    }

    // Notes
    @IBOutlet weak var itemNotes: UILabel!
}

GearComponentViewController的Stepper扩展

extension GearComponentViewController: Stepper {
    
    func refreshTableViewCell() {
        print("arrayPosition: \(arrayPosition)")
//        gearTableView.reloadData()
    }
    
    func stepperWasPressed(didIncrease: Bool, namePassed: String, userindexPath: Int) {
        
        if didIncrease {
            arrayPosition = userindexPath
            print("arrayPosition: \(arrayPosition)")
//            itemArray[userindexPath].quantity += 1
            print("userindexPath: \(userindexPath) -- namePassed: \(namePassed) -- didIncrease: \(didIncrease)")
            print("increase selected")
            refreshTableViewCell()
        }else {
            arrayPosition = userindexPath
            print("arrayPosition: \(arrayPosition)")
//            itemArray[userindexPath].quantity -= 1
            print("userindexPath: \(userindexPath) -- namePassed: \(namePassed) -- didIncrease: \(didIncrease)")
            print("decrease selected")
            refreshTableViewCell()
        }
    }
}

完整GearComponentViewController代码

import UIKit

class GearComponentViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    
    var arrayPosition: Int = 0
    
    // Data Sources
    var itemArray: [GearItem] = []
    var totalWeight1: Int = 0
    var totalWeight2: Int = 0
    var totalItems: Int = 0
    
    // Weight Label Outlets
    @IBOutlet weak var weight1LabelOutlet: UILabel!
    @IBOutlet weak var weight2LabelOutlet: UILabel!
    
    // Item Count Label Outlet
    @IBOutlet weak var totalCountLabelOutlet: UILabel!
    
    // TableView Outlet
    @IBOutlet weak var gearTableView: UITableView!
    
    // Bar Button Item
    @IBAction func addItemPressed(_ sender: Any) {
        presentAlert()
    }
    
    // View Did Load
    override func viewDidLoad() {
        super.viewDidLoad()
        gearTableView.dataSource = self
        gearTableView.delegate = self
        print("Gear Component View Controller successfully loaded.")
        
        updateUI()
    }
    
    //MARK: - UI Updater
    func updateUI() {
        addTotalWeight()
        addAllItems()
    }
    
    // Adding Total Weight
    func addTotalWeight() {
        var addedWeight1: Int = 0
        var addedWeight2: Int = 0
        
        for item in itemArray {
            addedWeight1 += item.weight1 * item.quantity
            addedWeight2 += item.weight2 * item.quantity
        }
        
        totalWeight1 = addedWeight1
        totalWeight2 = addedWeight2
        
        weight1LabelOutlet.text = String("\(totalWeight1)")
        weight2LabelOutlet.text = String("\(totalWeight2)")
    }
    
    // Item Count
    func addAllItems() {
        var addedValue: Int = 0
        
        for item in itemArray {
            addedValue += item.quantity
        }
        totalItems = addedValue
        
        totalCountLabelOutlet.text = String("\(totalItems)")
        print("Total items: \(totalItems)")
    }
    
    //MARK: - Alert
    func presentAlert() {
        let alertController = UIAlertController(title: "Add Item", message: "Enter item info here", preferredStyle: .alert)
        
        // AlertController Textfields
        alertController.addTextField { (textField) in
            textField.placeholder = "Name"
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Weight 1"
            textField.keyboardType = .decimalPad
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Weight 2"
            textField.keyboardType = .decimalPad
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Quanity"
            textField.keyboardType = .decimalPad
        }
        alertController.addTextField { (textField) in
            textField.placeholder = "Notes"
        }
        
        // AlertController Actions
        let continueAction = UIAlertAction(title: "Add", style: .default) { [self, weak alertController] _ in
            guard let textFields = alertController?.textFields else { return }
            
            if let userItemName = textFields[0].text,
               let userItemWeight1 = textFields[1].text,
               let userItemWeight2 = textFields[2].text,
               let userItemQuanity = textFields[3].text,
               let userItemNotes = textFields[4].text {
                
                print("Name: \(userItemName)")
                print("Item Weight 1: \(userItemWeight1)")
                print("Item Weight 2: \(userItemWeight2)")
                print("Item Quantity: \(userItemQuanity)")
                print("Item Notes: \(userItemNotes)")
                
                let userSubmittedItem = GearItem(itemName: userItemName, itemImage: UIImage(systemName: "photo.on.rectangle.angled")!, itemWeight1: Int(userItemWeight1) ?? 0, itemWeight2: Int(userItemWeight2) ?? 0, itemQuantity: Int(userItemQuanity) ?? 1, itemNotes: userItemNotes ?? "", creationPosition: itemArray.count + 1)
                
                self.itemArray.append(userSubmittedItem)
                gearTableView.reloadData()
                updateUI()
            }
        }
        
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
        cancelAction.setValue(UIColor.red, forKey: "titleTextColor")
        
        alertController.addAction(continueAction)
        alertController.addAction(cancelAction)
        
        self.present(alertController, animated: true)
    }
    
    //MARK: - TableView Protocol Functions
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return itemArray.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = gearTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! GearComponentTableViewCell
        cell.itemName.text = itemArray[indexPath.row].name
        cell.itemImage.image = itemArray[indexPath.row].image
        cell.itemWeight1.text = String("\(itemArray[indexPath.row].weight1)")
        cell.itemWeight2.text = String("\(itemArray[indexPath.row].weight2)")
        cell.itemQuanity.text = String("\(itemArray[indexPath.row].quantity)")
        cell.itemNotes.text = itemArray[indexPath.row].notes
        
        cell.tableViewCellPosition = indexPath.row
        
        return cell
    }
    
    //MARK: - Swipeable TableViewCell
    private func deleteItem(_ indexPath: IndexPath) {
        itemArray.remove(at: indexPath.row)
        gearTableView.reloadData()
        updateUI()
        print("Item Deleted")
    }
    
    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let action = UIContextualAction(style: .normal, title: "Delete") { [weak self] (action, view, completionHandler) in
            self?.deleteItem(indexPath)
            completionHandler(true)
        }
        action.backgroundColor = .systemRed
        
        return UISwipeActionsConfiguration(actions: [action])
    }
}

extension GearComponentViewController: Stepper {
    
    func refreshTableViewCell() {
        print("arrayPosition: \(arrayPosition)")
//        itemArray[arrayPosition].quantity += 1
//        gearTableView.reloadData()
    }
    
    func stepperWasPressed(didIncrease: Bool, namePassed: String, userindexPath: Int) {
        
        if didIncrease {
            arrayPosition = userindexPath
            print("arrayPosition: \(arrayPosition)")
//            itemArray[userindexPath].quantity += 1
            print("userindexPath: \(userindexPath) -- namePassed: \(namePassed) -- didIncrease: \(didIncrease)")
            print("increase selected")
            refreshTableViewCell()
        }else {
            arrayPosition = userindexPath
            print("arrayPosition: \(arrayPosition)")
//            itemArray[userindexPath].quantity -= 1
            print("userindexPath: \(userindexPath) -- namePassed: \(namePassed) -- didIncrease: \(didIncrease)")
            print("decrease selected")
            refreshTableViewCell()
        }
    }
}
问题原因与解决方案

核心问题

  1. 错误的ViewController实例引用:GearComponentTableViewCell中直接创建var mainVC = GearComponentViewController(),这是一个全新的空实例,而非当前显示的ViewController。这个新实例的itemArray为空,调用stepperWasPressed时访问数组必然越界。
  2. 索引失效风险:即使解决实例问题,删除Cell后,已复用Cell保存的tableViewCellPosition可能指向已被移除的索引,后续点击Stepper仍会触发越界。

修复步骤

步骤1:用代理模式替换直接创建实例

修改GearComponentTableViewCell,移除mainVC属性,改为弱引用代理:

class GearComponentTableViewCell: UITableViewCell {
    weak var delegate: Stepper?
    var tableViewCellPosition: Int! = nil

    // ... 其他属性和IBOutlet不变

    @IBAction func stepperPressed (_ sender: UIStepper!){
        guard let position = tableViewCellPosition, let delegate = delegate else { return }
        if sender.value == 1 {
            print("up and item: \(itemName.text!)");
            sender.value = 0
            delegate.stepperWasPressed(didIncrease: true, namePassed: itemName.text ?? "", userindexPath: position)
        } else if sender.value == -1 {
            print("down and item: \(itemName.text!)");
            sender.value = 0
            delegate.stepperWasPressed(didIncrease: false, namePassed: itemName.text ?? "", userindexPath: position)
        }
    }

    // ... 其他代码不变
}

步骤2:在ViewController中设置Cell代理

在cellForRowAt方法中添加代理赋值:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = gearTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! GearComponentTableViewCell
    // ... 其他赋值代码不变
    cell.delegate = self
    cell.tableViewCellPosition = indexPath.row
    return cell
}

步骤3:添加索引合法性校验

在stepperWasPressed方法中先校验索引有效性,同时避免数量减到负数:

func stepperWasPressed(didIncrease: Bool, namePassed: String, userindexPath: Int) {
    guard userindexPath >= 0 && userindexPath < itemArray.count else {
        print("Invalid index: \(userindexPath)")
        return
    }
    
    if didIncrease {
        itemArray[userindexPath].quantity += 1
    }else {
        if itemArray[userindexPath].quantity > 0 {
            itemArray[userindexPath].quantity -= 1
        }
    }
    print("userindexPath: \(userindexPath) -- namePassed: \(namePassed) -- didIncrease: \(didIncrease)")
    gearTableView.reloadRows(at: [IndexPath(row: userindexPath, section: 0)], with: .automatic)
    updateUI()
}

步骤4:修正拼写错误

代码中quanity为拼写错误,统一改为正确的quantity,确保模型与代码属性名一致。

内容的提问来源于stack exchange,提问作者slimeforest

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 14:30:24