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

SwiftUI调用API无法获取数据:错误原因与修复方案求助

解决Swift项目中API数据解析错误问题

核心问题分析

  1. API返回结构不匹配:你直接尝试解析成[Politician]数组,但实际API返回的是外层包含data字段的JSON对象,议员数组嵌套在data中。
  2. 可选字段未处理:API返回的birth_year可能为null,你的结构体将其定义为非可选Int,导致解析失败。
  3. 单个议员请求URL错误:fetchPolitician方法的URL缺少斜杠,正确路径应为/politicians/\(id)。
  4. 错误日志不完整:仅打印localizedDescription无法定位具体解析问题,需打印完整错误信息。

修正后的完整代码

import SwiftUI

// 定义API返回的外层列表结构
struct PoliticianResponse: Decodable {
    let data: [Politician]
}

// 定义API返回的单个议员详情外层结构
struct SinglePoliticianResponse: Decodable {
    let data: Politician
}

struct Politician: Decodable, Identifiable {
    let id: Int
    let firstName: String
    let lastName: String
    let birthYear: Int? // 改为可选类型,兼容API中可能为null的情况
    
    enum CodingKeys: String, CodingKey {
        case id
        case firstName = "first_name"
        case lastName = "last_name"
        case birthYear = "birth_year"
    }
}

class PoliticianService {
    func fetchPoliticians(completion: @escaping ([Politician]?) -> Void) {
        guard let url = URL(string: "https://www.abgeordnetenwatch.de/api/v2/politicians") else {
            completion(nil)
            return
        }
        
        URLSession.shared.dataTask(with: url) { data, _, error in
            guard let data = data else {
                completion(nil)
                return
            }
            
            do {
                let response = try JSONDecoder().decode(PoliticianResponse.self, from: data)
                completion(response.data)
            } catch {
                print("列表解析错误:", error) // 打印完整错误便于调试
                completion(nil)
            }
        }.resume()
    }
    
    func fetchPolitician(id: Int, completion: @escaping (Politician?) -> Void) {
        // 修正URL路径,添加斜杠
        guard let url = URL(string: "https://www.abgeordnetenwatch.de/api/v2/politicians/\(id)") else {
            completion(nil)
            return
        }
        
        URLSession.shared.dataTask(with: url) { data, _, error in
            guard let data = data else {
                completion(nil)
                return
            }
            
            do {
                let response = try JSONDecoder().decode(SinglePoliticianResponse.self, from: data)
                completion(response.data)
            } catch {
                print("详情解析错误:", error)
                completion(nil)
            }
        }.resume()
    }
}

class PoliticianListViewModel: ObservableObject {
    @Published var politicians = [Politician]()

    func fetchPoliticians() {
        guard let url = URL(string: "https://www.abgeordnetenwatch.de/api/v2/politicians") else { return }
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else {
                print(error?.localizedDescription ?? "未知错误")
                return
            }
            let decoder = JSONDecoder()
            do {
                let response = try decoder.decode(PoliticianResponse.self, from: data)
                DispatchQueue.main.async {
                    self.politicians = response.data
                }
            } catch {
                print("ViewModel解析错误:", error)
            }
        }.resume()
    }
}

struct ContentView: View {
    @StateObject var viewModel = PoliticianListViewModel()
    
    var body: some View {
        NavigationStack { // 替代NavigationView,适配iOS 16+
            List(viewModel.politicians) { politician in // 因Politician遵循Identifiable,无需显式指定id
                NavigationLink(destination: PoliticianDetailView(politician: politician)) {
                    Text("\(politician.firstName) \(politician.lastName)")
                }
            }
            .navigationTitle("议员列表")
        }
        .onAppear {
            viewModel.fetchPoliticians()
        }
    }
}

struct PoliticianDetailView: View {
    let politician: Politician
    
    var body: some View {
        VStack(spacing: 16) {
            Text("\(politician.firstName) \(politician.lastName)")
                .font(.title)
            // 处理可选的出生年份
            if let birthYear = politician.birthYear {
                Text("出生年份: \(birthYear)")
            } else {
                Text("出生年份: 未知")
            }
        }
        .navigationTitle("议员详情")
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

关键说明

  • 不需要包含API返回的所有字段,Codable协议会自动忽略未定义的字段,仅解析你声明的部分。
  • 打印完整错误信息是调试解析问题的核心,能直接看到字段类型不匹配、缺失等具体问题。
  • 让Politician遵循Identifiable协议后,List无需手动指定id参数,代码更简洁。
  • 适配iOS 16+使用NavigationStack替代NavigationView,更符合最新SwiftUI规范。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 11:17:18