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

Swift:JSON解码为自定义结构体并填充Table View问题排查

Swift TableView 填充API数据问题修复与改进建议

问题背景

我有自定义结构体作为数据模型,API返回JSON字典,需要将字典值填充到TableView单元格中。已创建包含键名的数组用于设置ViewController中标签内容,单元格包含文本框和标签,希望对应显示API返回的数据,但尝试解析为数组后没有任何数据加载,求改进建议。

核心问题分析

  • API接口返回单个用户对象,而非用户数组,原代码用[UserData].self解析会直接失败,导致无数据
  • TableView行数逻辑错误:原代码返回数组长度,但实际需求是每个用户属性对应一行,应返回属性标题数组的长度
  • 单元格赋值逻辑错误:仅固定显示first_name,未根据每行属性标题匹配对应数据
  • 变量命名不符合Swift规范(如DataTable、UserArr应使用小驼峰命名)
  • 未处理嵌套结构(如employment、address)和可选类型(如subscription)的展示

修复与改进步骤

  1. 修正API解析逻辑:将解析目标改为单个UserData对象,调整回调类型与数据存储变量
  2. 调整TableView数据源配置:行数改为属性标题数组长度,按行索引匹配标题与对应数据
  3. 实现数据属性动态映射:给UserData添加方法,根据属性标题返回对应字符串值,处理嵌套结构与可选值
  4. 规范变量命名:遵循Swift小驼峰规则,提升代码可读性
  5. 优化嵌套数据展示:将嵌套结构整理为可读字符串,处理可选值空值情况

修改后的代码实现

ViewController 代码

import UIKit

class FirstViewController: UIViewController {
    
    @IBOutlet weak var dataTable: UITableView!
    
    // 属性标题数组,对应每个单元格的标签文本
    let userPropertyTitles = ["ID", "UID", "Password", "First Name", "Last Name", "Username", "Email", "Avatar", "Gender", "Phone Number", "Social Insurance Number", "Date Of Birth", "Employment", "Address", "Zip Code", "State", "Country", "Coordinates", "CC Number", "Subscription"]
    
    // 存储单个用户数据(API返回单个对象)
    var currentUser: UserData?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        dataTable.dataSource = self
        dataTable.delegate = self
        
        NetworkManager.shared.fetchData { [weak self] userData in
            self?.currentUser = userData
            DispatchQueue.main.async {
                self?.dataTable.reloadData()
            }
        }
    }
}

extension FirstViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // 每个属性对应一行,返回标题数组的长度
        return userPropertyTitles.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! DataTableViewCell
        
        let title = userPropertyTitles[indexPath.row]
        cell.txtLabel.text = title
        
        // 根据标题获取对应的数据值,空值显示N/A
        cell.txtField.text = currentUser?.value(forPropertyTitle: title) ?? "N/A"
        
        return cell
    }
}

UserData 模型代码

import Foundation

struct UserData: Codable {
    let id: Int
    let uid: String
    let password: String
    let first_name: String
    let last_name: String
    let username: String
    let email: String
    let avatar: String
    let gender: String
    let phone_number: String
    let social_insurance_number: String
    let date_of_birth: String
    let employment: Employment
    let address: Address
    let credit_card: CreditCard
    let subscription: Subscription?
    
    struct Employment: Codable {
        let title: String
        let key_skill: String
    }
    
    struct Address: Codable {
        let city: String
        let street_name: String
        let street_address: String
        let zip_code: String
        let state: String
        let country: String
        let coordinates: Coordinates
        
        struct Coordinates: Codable {
            let lat: Double
            let lng: Double
        }
    }
    
    struct CreditCard: Codable {
        let cc_number: String
    }
    
    struct Subscription: Codable {
        let plan: String
        let status: String
        let payment_method: String
        let term: String
    }
    
    // 根据属性标题返回对应的值字符串
    func value(forPropertyTitle title: String) -> String {
        switch title {
        case "ID": return String(id)
        case "UID": return uid
        case "Password": return password
        case "First Name": return first_name
        case "Last Name": return last_name
        case "Username": return username
        case "Email": return email
        case "Avatar": return avatar
        case "Gender": return gender
        case "Phone Number": return phone_number
        case "Social Insurance Number": return social_insurance_number
        case "Date Of Birth": return date_of_birth
        case "Employment": return "\(employment.title) - \(employment.key_skill)"
        case "Address": return "\(address.street_address), \(address.street_name), \(address.city)"
        case "Zip Code": return address.zip_code
        case "State": return address.state
        case "Country": return address.country
        case "Coordinates": return "Lat: \(address.coordinates.lat), Lng: \(address.coordinates.lng)"
        case "CC Number": return credit_card.cc_number
        case "Subscription": 
            guard let sub = subscription else { return "No subscription" }
            return "\(sub.plan) (\(sub.status))"
        default: return "N/A"
        }
    }
}

NetworkManager 代码

import Foundation
import Alamofire

class NetworkManager {
    static let shared = NetworkManager()
    private init() {}
    
    func fetchData(handler: @escaping (_ apiData: UserData) -> Void) {
        guard let url = URL(string: "https://random-data-api.com/api/v2/users") else {
            print("Invalid URL")
            return
        }
        
        // 使用Alamofire自带的Decodable解析,简化代码
        AF.request(url)
            .responseDecodable(of: UserData.self) { response in
                switch response.result {
                case .success(let userData):
                    handler(userData)
                case .failure(let error):
                    print("API Request Error: \(error.localizedDescription)")
                }
            }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 16:35:02