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

Swift中车辆JSON响应的History模型构建与TableView展示方案

解决TableView中车辆数据History字段多类型的问题

我来帮你一步步搞定这个API返回多类型字段的问题,分三个核心部分梳理解决方案:

1. 修改VehicleDataModel的History结构

首先得调整模型来适配history的两种类型(数组/字符串"None")。原来的模型里history是必填数组,这显然扛不住API返回字符串的情况,所以咱们把它改成可选数组,同时给模型加上Codable协议支持,再通过自定义解码逻辑来处理两种类型的转换:

struct VehicleDataModel: Codable {
    let registrationNo: String?
    let engineCC: String?
    let engineCCID: String?
    let vehicleID: String?
    let history: [HistoryModel]? // 改成可选数组,适配"None"场景

    struct HistoryModel: Codable {
        let packages: String?
        let billDiscount: String?
        let dateTime: String?
        let billPaid: String?
        let billAmount: String?

        // 映射API的蛇形命名字段
        enum CodingKeys: String, CodingKey {
            case packages
            case billDiscount = "bill_discount"
            case dateTime = "date_time"
            case billPaid = "bill_paid"
            case billAmount = "bill_amount"
        }
    }

    // 映射VehicleDataModel的字段
    enum CodingKeys: String, CodingKey {
        case registrationNo = "registration_no"
        case engineCC = "engincc"
        case engineCCID = "enginccID"
        case vehicleID = "vehicleID"
        case history
    }

    // 自定义解码逻辑,处理history的两种类型
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        registrationNo = try container.decodeIfPresent(String.self, forKey: .registrationNo)
        engineCC = try container.decodeIfPresent(String.self, forKey: .engineCC)
        engineCCID = try container.decodeIfPresent(String.self, forKey: .engineCCID)
        vehicleID = try container.decodeIfPresent(String.self, forKey: .vehicleID)

        // 先尝试解码成数组,失败则处理字符串
        do {
            history = try container.decodeIfPresent([HistoryModel].self, forKey: .history)
        } catch {
            let historyStr = try container.decode(String.self, forKey: .history)
            // 如果是"None"就设为nil,也可以改成空数组,看你需求
            history = historyStr == "None" ? nil : nil
        }
    }
}

// 别忘了定义顶层响应模型,对应API的整体结构
struct VehicleResponse: Codable {
    let response: String?
    let accountType: String?
    let carData: [VehicleDataModel]?

    enum CodingKeys: String, CodingKey {
        case response
        case accountType = "account_type"
        case carData = "car_data"
    }
}

2. 通过Alamofire完成数据映射

推荐用Codable配合Alamofire的responseDecodable自动解析,比手动抠字段靠谱多了,代码也简洁:

func callApi() {
    let url = "http://esspk.net/production/20m/Api/getVehicleApi"
    let userID = UserDefaults.standard.integer(forKey: "user_id")
    let param = ["user_id": userID]
    
    AF.request(url, method: .post, parameters: param)
        .validate() // 自动校验HTTP状态码
        .responseDecodable(of: VehicleResponse.self) { response in
            switch response.result {
            case .success(let res):
                // 拿到车辆数据数组,赋值给数据源
                if let carList = res.carData {
                    self.vehicalModel = carList
                    // 主线程刷新TableView
                    DispatchQueue.main.async {
                        self.myVehicleTblView.reloadData()
                    }
                }
            case .failure(let error):
                print("API调用翻车了:\(error.localizedDescription)")
            }
        }
}

要是你想保留原来的手动解析方式(虽然不推荐),可以这么处理history字段:

func callApi() {
    let url = "http://esspk.net/production/20m/Api/getVehicleApi"
    let userID = UserDefaults.standard.integer(forKey: "user_id")
    let param = ["user_id" : userID]
    print(param)
    
    ServerCall.makeCallWitoutFile(url, params: param, type: Method.POST, currentView: nil) { (response) in
        if let json = response {
            print(json)
            if let carData = json["car_data"].array {
                var vehicleList = [VehicleDataModel]()
                for cData in carData {
                    let regNo = cData["registration_no"].string
                    let enginCC = cData["engincc"].string
                    let enginID = cData["enginccID"].string
                    let vehicleID = cData["vehicleID"].string
                    
                    var history: [VehicleDataModel.HistoryModel]? = nil
                    // 判断history的类型
                    if let historyArr = cData["history"].array {
                        // 是数组,转成HistoryModel列表
                        var historyModels = [VehicleDataModel.HistoryModel]()
                        for item in historyArr {
                            let pkg = item["packages"].string ?? ""
                            let discount = item["bill_discount"].string ?? ""
                            let date = item["date_time"].string ?? ""
                            let paid = item["bill_paid"].string ?? ""
                            let amount = item["bill_amount"].string ?? ""
                            let model = VehicleDataModel.HistoryModel(packages: pkg, billDiscount: discount, dateTime: date, billPaid: paid, billAmount: amount)
                            historyModels.append(model)
                        }
                        history = historyModels
                    } else if let historyStr = cData["history"].string, historyStr == "None" {
                        // 是"None"字符串,设为nil
                        history = nil
                    }
                    
                    let vehicle = VehicleDataModel(registrationNo: regNo, engineCC: enginCC, engineCCID: enginID, vehicleID: vehicleID, history: history)
                    vehicleList.append(vehicle)
                }
                self.vehicalModel = vehicleList
                DispatchQueue.main.async {
                    self.myVehicleTblView.reloadData()
                }
            }
        }
    }
}

3. 判断History是否存在并在TableView展示

在TableView的数据源方法里,判断当前车辆的history是否存在,然后展示对应的内容就行:

// 假设你的自定义Cell有显示车辆信息和历史记录的标签
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "VehicleCell", for: indexPath) as! VehicleCell
    let vehicle = vehicalModel[indexPath.row]
    
    // 填充车辆基本信息
    cell.regNoLabel.text = vehicle.registrationNo ?? "无车牌"
    cell.engineCCLabel.text = vehicle.engineCC ?? "未知排量"
    
    // 处理历史记录展示
    if let history = vehicle.history, !history.isEmpty {
        // 有历史记录,显示数量或第一条详情
        cell.historyStatusLabel.text = "历史记录:\(history.count)条"
        let firstRecord = history[0]
        cell.historyDetailLabel.text = "最近一次:\(firstRecord.dateTime ?? ""),金额:\(firstRecord.billAmount ?? "")"
    } else {
        // 无历史记录
        cell.historyStatusLabel.text = "无历史记录"
        cell.historyDetailLabel.text = ""
    }
    
    return cell
}

如果需要展开显示所有历史记录,可以在didSelectRowAt里处理,或者用TableView的折叠功能,这里就不展开讲啦。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 06:31:13