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

iOS开发:添加条目时触发Invalid update分段数量错误

问题描述

正在完成Big Nerd Ranch的开发挑战,需实现一款可添加随机条目并分为2个分段的App,无条目时显示“No Items”。初始状态为1个带“No Items”标题的分段,添加条目后需更新为标题分别为“50 AND UNDER”和“OVER 50”的两个分段。

添加条目时,在addNewItem方法内调用tableView.insertRows(at: [indexPath], with: .automatic)触发如下错误:

Thread 1: "Invalid update: invalid number of sections. The number of sections contained in the table view after the update (2) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted). Table view: <UITableView: 0x141012c00; frame = (0 0; 393 852); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x600003648840>; backgroundColor = <UIDynamicSystemColor: 0x600002d61240; name = tableBackgroundColor>; layer = <CALayer: 0x600003817400>; contentOffset: {0, -59}; contentSize: {393, 110}; adjustedContentInset: {59, 0, 34, 0}; dataSource: <LootLogger.ItemsViewController: 0x140106ab0>>"

调试发现触发addNewItem后,sections已更新为2,添加新行流程中numberOfSections和numberOfRowsInSection均已更新,但在进入cellForRowAt前报错,所有添加行的先决条件似乎都已正确更新。

附上ItemsViewController完整代码:

import UIKit

class ItemsViewController: UITableViewController {
    // add ItemStore property to have access to store that holds all items
    var itemStore: ItemStore!
    var sections = 1

    @IBAction func addNewItem(_ sender: UIButton){
        let newItem = itemStore.createItem()
        sections = 2
        if newItem.valueInDollars > 50 {
            if let index = itemStore.itemsOverFifty.firstIndex(of: newItem){
                let indexPath = IndexPath(row: index, section: 1)
                tableView.insertRows(at: [indexPath], with: .automatic)
            }
        } else {
            if let index = itemStore.itemsFiftyAndUnder.firstIndex(of: newItem){
                let indexPath = IndexPath(row: index, section: 0)
                tableView.insertRows(at: [indexPath], with: .automatic)
            }
        }
    }

    @IBAction func toggleEditingMode(_ sender: UIButton){
        if isEditing{
            sender.setTitle("Edit", for: .normal)
            setEditing(false, animated: true)
        } else {
            sender.setTitle("Done", for: .normal)
            setEditing(true, animated: true)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // MARK: - Table view data source

    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if sections == 1 {
            return "No Items!"
        }
        else if section == 0 {
            return "50 AND UNDER"
        } else {
            return "OVER 50"
        }
    }

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

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if sections == 1 {
            return 0
        } else {
            if section == 0 {
                return itemStore.itemsFiftyAndUnder.count
            } else {
                return itemStore.itemsOverFifty.count
            }
        }
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
        let item = indexPath.section == 0 ? itemStore.itemsFiftyAndUnder[indexPath.row] : itemStore.itemsOverFifty[indexPath.row]

        cell.textLabel?.text = item.name
        cell.detailTextLabel?.text = "$\(item.valueInDollars)"

        return cell
    }

    // Override to support editing the table view.
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            let item = indexPath.section == 0 ? itemStore.itemsFiftyAndUnder[indexPath.row] : itemStore.itemsOverFifty[indexPath.row]
            itemStore.removeItem(item)
            tableView.deleteRows(at: [indexPath], with: .fade)
        }
    }

    // Override to support rearranging the table view.
    override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to toIndexPath: IndexPath) {
        itemStore.moveItem(from: fromIndexPath.row, to: toIndexPath.row)
    }
}
问题分析与解决

核心问题

直接修改sections变量后调用insertRows,但未通知表格视图分段数量发生变化。表格视图的动画更新需严格遵循规则:若分段数从1变为2,必须先通过insertSections告知表格视图添加了新分段,再插入行;或用performBatchUpdates包裹分段更新与行更新操作,确保数据模型与UI同步。

修复步骤

  1. 移除直接赋值sections = 2的操作,改为在批量更新中处理分段变化
  2. 使用tableView.performBatchUpdates包裹分段插入和行插入操作,保证表格视图正确追踪数据变化
  3. 优化sections变量逻辑,让它根据itemStore中的数据自动判断,避免手动维护状态

修改后的addNewItem方法

@IBAction func addNewItem(_ sender: UIButton){
    let newItem = itemStore.createItem()
    
    tableView.performBatchUpdates({
        // 初始状态下添加第二个分段
        if sections == 1 {
            sections = 2
            tableView.insertSections(IndexSet(integer: 1), with: .automatic)
        }
        
        // 插入新行
        if newItem.valueInDollars > 50 {
            if let index = itemStore.itemsOverFifty.firstIndex(of: newItem){
                let indexPath = IndexPath(row: index, section: 1)
                tableView.insertRows(at: [indexPath], with: .automatic)
            }
        } else {
            if let index = itemStore.itemsFiftyAndUnder.firstIndex(of: newItem){
                let indexPath = IndexPath(row: index, section: 0)
                tableView.insertRows(at: [indexPath], with: .automatic)
            }
        }
    }, completion: nil)
}

额外优化:自动判断分段数量

将sections改为计算属性,根据itemStore内容自动计算,减少状态不一致风险:

var sections: Int {
    return itemStore.items.isEmpty ? 1 : 2
}

此时numberOfSections直接返回该计算属性即可,无需手动赋值。

补充:删除所有条目时恢复分段状态

当所有条目被删除时,需恢复为1个分段状态,因此在删除方法中添加对应逻辑:

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete {
        let item = indexPath.section == 0 ? itemStore.itemsFiftyAndUnder[indexPath.row] : itemStore.itemsOverFifty[indexPath.row]
        itemStore.removeItem(item)
        
        tableView.performBatchUpdates({
            tableView.deleteRows(at: [indexPath], with: .fade)
            
            // 所有条目被删除时,删除第二个分段
            if itemStore.items.isEmpty && sections == 2 {
                sections = 1
                tableView.deleteSections(IndexSet(integer: 1), with: .automatic)
            }
        }, completion: nil)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 06:02:02