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

Swift 4与Xcode 9中解析嵌套JSON数组实现ExpandableTableView

解决iOS ExpandableTableView的JSON解析错误问题

嘿,刚看到你遇到的这个JSON解析错误,其实问题很明确——你的JSON根结构是数组,但你却按单个字典来解析了,所以才会抛出typeMismatch(Dictionary<String, Any>, ...)的错误。我来一步步帮你搞定:

1. 先明确错误根源

你说数据是嵌套数组形式,比如你的JSON大概长这样(举个例子):

[
  {
    "headerTitle": "水果分类",
    "items": [{"name": "苹果"}, {"name": "香蕉"}]
  },
  {
    "headerTitle": "蔬菜分类",
    "items": [{"name": "白菜"}, {"name": "胡萝卜"}]
  }
]

这个JSON的根是数组(外面包着[]),但你用JSONDecoder().decode(DataModelClass.self, from: data)解析时,DataModelClass是对应单个字典的结构体,解码器期望拿到一个字典,实际却是数组,自然就报错了。

2. 修正解析模型和代码

首先,你需要定义匹配JSON结构的模型:

// 父项模型(对应数组里的每个元素,作为TableView表头)
struct Category: Codable {
    let headerTitle: String
    let items: [Item]
    
    // 如果JSON字段和属性名不一致,用CodingKeys映射,比如你想把headerTitle改成title:
    // enum CodingKeys: String, CodingKey {
    //     case title = "headerTitle"
    //     case items
    // }
}

// 子项模型(对应可展开的表格元素)
struct Item: Codable {
    let name: String
    // 这里根据你的JSON字段添加其他属性
}

然后修正解析代码,把解析目标改成数组类型:

// 假设你已经获取到了jsonData
do {
    // 重点:这里用[Category].self而不是Category.self
    let categories = try JSONDecoder().decode([Category].self, from: jsonData)
    // 把解析好的数据存到你的数据源里,比如:
    self.categories = categories
    // 同时初始化一个数组记录每个分类的展开状态,默认全部折叠
    self.expandedStates = Array(repeating: false, count: categories.count)
    self.tableView.reloadData()
} catch {
    print("解析出错:\(error.localizedDescription)")
}

3. 适配ExpandableTableView的数据源逻辑

接下来把这些数据用到可展开表格里,核心是控制每个section的行数和单元格展示:

首先定义两个数据源变量:

var categories: [Category] = []
var expandedStates: [Bool] = [] // 记录每个分类是否展开

然后实现TableView的核心代理方法:

// 每个section对应一个父项
func numberOfSections(in tableView: UITableView) -> Int {
    return categories.count
}

// 计算每个section的行数:展开时显示1个表头+所有子项,折叠时只显示表头
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if expandedStates[section] {
        return 1 + categories[section].items.count
    } else {
        return 1
    }
}

// 配置单元格:区分表头行和子项行
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let category = categories[indexPath.section]
    
    if indexPath.row == 0 {
        // 表头单元格
        let cell = tableView.dequeueReusableCell(withIdentifier: "HeaderCell", for: indexPath)
        cell.textLabel?.text = category.headerTitle
        // 添加展开/折叠的指示图标
        cell.accessoryType = expandedStates[indexPath.section] ? .disclosureIndicator : .none
        return cell
    } else {
        // 子项单元格
        let item = category.items[indexPath.row - 1] // 因为row=0是表头,所以子项从row-1取
        let cell = tableView.dequeueReusableCell(withIdentifier: "ChildCell", for: indexPath)
        cell.textLabel?.text = item.name
        return cell
    }
}

// 点击表头时切换展开状态
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if indexPath.row == 0 {
        expandedStates[indexPath.section].toggle()
        // 刷新当前section,带动画效果
        tableView.reloadSections(IndexSet(integer: indexPath.section), with: .automatic)
    }
}

4. 额外注意事项

  • 确保你的TableView注册了对应的单元格(HeaderCell和ChildCell),可以在viewDidLoad里注册:
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "HeaderCell")
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "ChildCell")
  • 如果你的JSON字段和模型属性名完全不一致,一定要用CodingKeys做映射,避免解析失败。
  • 可以给表头单元格添加自定义样式,比如加粗文字、右侧的展开/折叠图标,提升用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:22:21