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)
}
}
}
}
}
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
}
}