How to use extern Structure in a View correctly?

34 views Asked by At

I couldn't find a way to use my Structure in a ContentView. Structure fills correctly, but that's it. How can I replace example array in a View:

let countries = ["Germany", "Egypt", "Italy"] // example

with a real array of Countries from:

let countries: CountriesWikiData = await decodeWikiData(from: countriesQuery)
struct CountriesWikiData: Decodable {
    let head: CountriesHead
    let results: CountriesResults
}

struct CountriesHead: Decodable {
    let vars: [String]
}

struct CountriesResults: Decodable {
    let bindings: [CountriesBindings]
}

struct CountriesBindings: Decodable {
    let country: [String: String]
    let countryLabel: [String: String]
}


func decodeWikiData<T: Decodable>(from url: String) async -> T {
    
    let url = URL(string: url)!
    let request = URLRequest(url: url)
    
    do {
        let (data, _) = try await URLSession.shared.data(for: request)
        guard
            let jsonObj = try? JSONSerialization.jsonObject(with: data, options: []),
            let jsonData = try? JSONSerialization.data(withJSONObject: jsonObj, options: .prettyPrinted)
        else {
            fatalError("Cannot convert data to JSON.")
        }
        guard let strc = try? JSONDecoder().decode(T.self, from: jsonData)
        else {
            fatalError("Cannot make a structure.")
        }
        return strc // Wheeeee!!
    } catch {
        fatalError("Cannot get data from URL.")
    }
}


struct ContentView: View {
        
    /*
    SELECT DISTINCT ?country ?countryLabel
    WHERE
    {
      ?country wdt:P31 wd:Q3624078.
      SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
    }
    */
    
    let countriesQuery = "https://query.wikidata.org/sparql?query=SELECT%20DISTINCT%20%3Fcountry%20%3FcountryLabel%0AWHERE%0A%7B%0A%20%20%3Fcountry%20wdt%3AP31%20wd%3AQ3624078.%0A%20%20SERVICE%20wikibase%3Alabel%20%7B%20bd%3AserviceParam%20wikibase%3Alanguage%20%22en%22%20%7D%0A%7D&format=json"
    
    let countries = ["Germany", "Egypt", "Italy"] // example
    @State var selectedCountry = ""
    
    //let countries: CountriesWikiData = await decodeWikiData(from: countriesQuery)

    var body: some View {
        VStack {
            Picker("", selection: $selectedCountry) {
                ForEach(countries, id: \.self) {
                    Text($0)
                }
            }
        }
    }
}

1

There are 1 answers

0
workingdog support Ukraine On BEST ANSWER

You could try this approach to display the results of the API call using the model structs as shown in the example code.

The approach uses a modified CountriesBindings with Country and CountryLabel structs. An updated func decodeWikiData(...) in a .task {...} view modifier and a Picker with the important .tag(country.value)

struct CountriesWikiData: Decodable {
    let head: CountriesHead
    let results: CountriesResults
}

struct CountriesHead: Decodable {
    let vars: [String]
}

struct CountriesResults: Decodable {
    let bindings: [CountriesBindings]
}

struct CountriesBindings: Codable {
    let country: Country
    let countryLabel: CountryLabel
}

struct Country: Identifiable, Codable, Hashable {
    let id = UUID()
    let type: String
    let value: String
}

struct CountryLabel: Identifiable, Codable, Hashable {
    let id = UUID()
    let xmlLang: String
    let type: String
    let value: String

    enum CodingKeys: String, CodingKey {
        case xmlLang = "xml:lang"
        case type, value
    }
}

struct ContentView: View {
    let countriesQuery = "https://query.wikidata.org/sparql?query=SELECT%20DISTINCT%20%3Fcountry%20%3FcountryLabel%0AWHERE%0A%7B%0A%20%20%3Fcountry%20wdt%3AP31%20wd%3AQ3624078.%0A%20%20SERVICE%20wikibase%3Alabel%20%7B%20bd%3AserviceParam%20wikibase%3Alanguage%20%22en%22%20%7D%0A%7D&format=json"
    
    @State var countries: [CountryLabel] = []
    @State var selectedCountry: String = ""

    var body: some View {
        VStack {
            Text("selected country: \(selectedCountry)")
            Picker("", selection: $selectedCountry) {
                ForEach(countries) { country in
                    Text(country.value).tag(country.value)
                }
            }
        }
        .task {
            let response: CountriesWikiData? = await decodeWikiData(from: countriesQuery)
            if let resp = response {
                for bin in resp.results.bindings {
                    countries.append(bin.countryLabel)
                }
            }
            if let first = countries.first {
                selectedCountry = first.value
            }
        }
    }
    
    func decodeWikiData<T: Decodable>(from url: String) async -> T? {
        if let url = URL(string: url) {
            do {
                let (data, _) = try await URLSession.shared.data(from: url)
                return try JSONDecoder().decode(T.self, from: data)
            } catch {
                print(error)
            }
        }
        return nil
    }
    
}