为什么我的内部 API 结构无法在 SwiftUI 中成功执行?

Why isn't my internal API structure executing successfully in SwiftUI?

提问人:Alko Nama 提问时间:8/26/2023 更新时间:8/26/2023 访问量:57

问:

我是一个菜鸟,我对自己的问题感到困惑。我的理解可能有限,我查找了一些类似的问题,我发现了不同的问题,比如我缺少 init(我不知道 init 是如何工作的)。

所以!我尝试通过JSON使用外部API。你可以在这里找到它(https://www.abgeordnetenwatch.de/api/v2/politicians)。我的代码工作正常,直到我想在我工作的“政治家”结构旁边添加一个内部结构“party”(在我的代码“partei”中)。经过研究,我认为我需要像我的代码一样构建代码结构。我为 id 和 label 使用了不同的名称,以使其通过编码键工作。

我目前遇到这些错误

“58:28 无法推断通用参数'T' ->在调用函数 'decode(_:from:)' (Foundation.JSONDecoder) 时

58:43 无法将类型“模块”的值转换为预期的参数类型“T.Type”

108:88 调用中缺少参数“partei”的参数 ->'init(politician:partei:)'在这里宣布

我的计划是将内部结构 Partei 中的另一个文本添加到我的 PoliticanDetailView 中

        List(viewModel.parteien, id: \.parteiID) { partei in
            Text("Partei: \(partei.namePartei)")

我的整个混乱代码,如果它可能不符合礼仪,我很抱歉

import SwiftUI

struct Partei: Decodable {
    let parteiID: Int
    let namePartei: String

    private enum CodingKeys: String, CodingKey {
        case parteiID = "id"
        case namePartei = "label"
    }
}

struct Politician: Decodable {
    let id: Int
    let firstName: String
    let lastName: String
    let birthYear: Int?
    let beruf: String?
    let geschlecht: String?
    let partei: Partei
    
    enum CodingKeys: String, CodingKey {
        case id
        case firstName = "first_name"
        case lastName = "last_name"
        case birthYear = "year_of_birth"
        case beruf = "occupation"
        case geschlecht = "sex"
        case partei
    }
    
}


struct Politicians: Decodable {
    let data: [Politician]
}

actor PoliticianDataService {
    func fetchPoliticians() async throws -> [Politician] {
        guard let url = URL(string: "https://www.abgeordnetenwatch.de/api/v2/politicians") else { return [] }
        
        let (data, _) = try await URLSession.shared.data(from: url)
        
        let decoder = JSONDecoder()
        let politicians = try decoder.decode(Politicians.self, from: data)
        return politicians.data
    }
}

actor ParteiDataService {
    func fetchParteien() async throws -> [Partei] {
        guard let url = URL(string: "https://www.abgeordnetenwatch.de/api/v2/parties") else { return [] }

        let (data, _) = try await URLSession.shared.data(from: url)
        
        let decoder = JSONDecoder()
        let parteien = try decoder.decode(Parteien.self, from: data)
        return parteien.data
    }
}

@MainActor
class PoliticianListViewModel: ObservableObject {
    @Published var politicians = [Politician]()
    
    private var dataService = PoliticianDataService()
    
    init() {
        // Additional setup could be done here if needed
    }
    
    func fetchPoliticians() {
        Task {
            do {
                let fetchedPoliticians = try await dataService.fetchPoliticians()
                politicians = fetchedPoliticians
            } catch {
                print(error)
            }
        }
    }
}

class PoliticianDetailViewModel: ObservableObject {
    @Published var parteien = [Partei]()

    private var dataService = ParteiDataService()

    func fetchParteien() {
        Task {
            do {
                let fetchedParteien = try await dataService.fetchParteien()
                parteien = fetchedParteien
            } catch {
                print(error)
            }
        }
    }
}

struct ContentView: View {
    @StateObject var viewModel = PoliticianListViewModel()
    
    var body: some View {
        NavigationView {
            List(viewModel.politicians, id: \.id) { politician in
                NavigationLink(destination: PoliticianDetailView(politician: politician)) {
                    Text("\(politician.firstName) \(politician.lastName)")
                }
            }
            .navigationTitle("Politiker in Deutschland")
        }
        .onAppear {
            viewModel.fetchPoliticians()
        }
    }
}

struct PoliticianDetailView: View {
    let politician: Politician
    let partei: Partei
    

    @StateObject private var viewModel = PoliticianDetailViewModel()  // Create a ViewModel

    var body: some View {
        VStack {
            Text("Name: \(politician.firstName) \(politician.lastName)")
            Text("Geburtsjahr \(politician.birthYear ?? 0)")
            Text("Alter: \(calculateAge(birthYear: politician.birthYear ?? 0)) Jahre")
            Text("Beschäftigt als \(politician.beruf ?? "Unbekannt")")
            Text("Geschlecht: \(politician.geschlecht ?? "Unbekannt")")

            List(viewModel.parteien, id: \.parteiID) { partei in
                Text("Partei: \(partei.namePartei)")
            }
            .onAppear {
                viewModel.fetchParteien()
            }
        }
        .navigationTitle("\(politician.firstName) \(politician.lastName)")
    }

    func calculateAge(birthYear: Int) -> Int {
        let currentYear = Calendar.current.component(.year, from: Date())
        return currentYear - birthYear
    }
}

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

iOS JSON Swift SwiftUI

评论

0赞 Alko Nama 8/26/2023
List(viewModel.parteien, id: \.parteiID) { partei in Text(“Partei: (partei.namePartei)”)-> IDK 关于这个,我在某处找到了这个,我不想创建一个列表,而是一个简单的文本,但我遇到了很多错误

答:

2赞 Paulw11 8/26/2023 #1

您的代码中有一些错误。

第一个错误的发生是因为您想要解码,但没有结构。Parteien.selfParteien

struct Parteien: Decodable {
    let data: [Partei]
}

但是,你并不真正需要这个结构或获取参与方,因为参与方已经在结构中。Politician

发生第二个错误的原因是,您已在结构中声明了一个属性,但未传入值。同样,您不需要该属性,因为它已经在 .parteiPoliticianDetailViewPolitician

与其使用 a,不如使用额外的 .ListText

struct PoliticianDetailView: View {
    let politician: Politician

    var body: some View {
        VStack {
            Text("Name: \(politician.firstName) \(politician.lastName)")
            Text("Geburtsjahr \(politician.birthYear ?? 0)")
            Text("Alter: \(calculateAge(birthYear: politician.birthYear ?? 0)) Jahre")
            Text("Beschäftigt als \(politician.beruf ?? "Unbekannt")")
            Text("Geschlecht: \(politician.geschlecht ?? "Unbekannt")")

            Text("Party: \(politician.partei.namePartei)")
        }
        .navigationTitle("\(politician.firstName) \(politician.lastName)")
    }

    func calculateAge(birthYear: Int) -> Int {
        let currentYear = Calendar.current.component(.year, from: Date())
        return currentYear - birthYear
    }
}

最后,你会得到一个运行时错误,因为你的 for 没有将属性从 JSON 映射到你的结构中:CodingKeysParteipartypartei

struct Politician: Decodable {
    let id: Int
    let firstName: String
    let lastName: String
    let birthYear: Int?
    let beruf: String?
    let geschlecht: String?
    let partei: Partei
    
    enum CodingKeys: String, CodingKey {
        case id
        case firstName = "first_name"
        case lastName = "last_name"
        case birthYear = "year_of_birth"
        case beruf = "occupation"
        case geschlecht = "sex"
        case partei = "party"
    }
}

此外,您可以调整您的年龄计算,使其不会将没有出生年份的人显示为 2023 年

struct PoliticianDetailView: View {
    let politician: Politician
    
    var body: some View {
        VStack {
            Text("Name: \(politician.firstName) \(politician.lastName)")
            Text("Geburtsjahr \(politician.birthYear ?? 0)")
            Text("Alter: \(calculateAge(birthYear: politician.birthYear))")
            Text("Beschäftigt als \(politician.beruf ?? "Unbekannt")")
            Text("Geschlecht: \(politician.geschlecht ?? "Unbekannt")")
            
            Text("Party: \(politician.partei.namePartei)")
        }
        .navigationTitle("\(politician.firstName) \(politician.lastName)")
    }
    
    func calculateAge(birthYear: Int?) -> String {
        guard let birthYear else {
            return "---"
        }
        let currentYear = Calendar.current.component(.year, from: Date())
        return String(currentYear - birthYear)+" jahre"
    }
}

并添加按姓氏排序:

actor PoliticianDataService {
    func fetchPoliticians() async throws -> [Politician] {
        guard let url = URL(string: "https://www.abgeordnetenwatch.de/api/v2/politicians") else { return [] }
        
        let (data, _) = try await URLSession.shared.data(from: url)
        
        let decoder = JSONDecoder()
        let politicians = try decoder.decode(Politicians.self, from: data)
        return politicians.data.sorted {
            $0.lastName < $1.lastName
        }
    }
}

评论

0赞 Alko Nama 8/26/2023
首先,感谢您的努力,我今晚将彻底检查这一点。不幸的是,我仍然遇到错误“13:88 调用中缺少参数'partei'的参数”。在这一行中,“var body: some View { NavigationView { List(viewModel.politicians, id: \.id) { politician in NavigationLink(destination: PoliticianDetailView(politician: politician))”。我只想列出政治家。
0赞 Paulw11 8/27/2023
您不得从let partei: ParteiPoliticianView
0赞 Alko Nama 8/29/2023
谢谢!我花时间重读了所有内容,我理解了其中的逻辑。我重新编写了我的代码,它起作用了。如果你不介意,我还有另一个问题。如果我想解码包含对另一个外部 API 的引用的 api,该怎么办?就像在底部的这个页面上一样,他们将添加 Fraction 和 Poll 等引用。
0赞 Alko Nama 8/29/2023
abgeordnetenwatch.de/api/entitaeten/vote
0赞 Paulw11 8/29/2023
您可以同时获取该数据并在显示时引用该数据,或者在投票的情况下,您可以在显示该投票的详细信息时获取数据。