SwiftUI调用API无法获取数据:错误原因与修复方案求助
解决Swift项目中API数据解析错误问题
核心问题分析
- API返回结构不匹配:你直接尝试解析成
[Politician]数组,但实际API返回的是外层包含data字段的JSON对象,议员数组嵌套在data中。 - 可选字段未处理:API返回的
birth_year可能为null,你的结构体将其定义为非可选Int,导致解析失败。 - 单个议员请求URL错误:
fetchPolitician方法的URL缺少斜杠,正确路径应为/politicians/\(id)。 - 错误日志不完整:仅打印
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
相关产品推荐
相关产品推荐

