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
相关产品推荐
相关产品推荐

