I looked at the other StackOverFlow answers but none of them worked in allowing me to make an API call using the Codable struct as shown below:
struct Response: Codable {
var value: [Result]
}
struct Result: Identifiable, Codable {
let id = UUID()
var BusStopCode: String!
var RoadName: String!
}
struct ContentView: View {
@State private var value = [Result]()
//var locations = [Locations]()
var body: some View {
List(value, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.BusStopCode)
.font(.headline)
Text(item.RoadName)
}
}
.task {
await loadData()
}
}
func loadData() async {
guard let url = URL(string: "google.com") //link replaced due to privacy reasons
else { print("Invalid URL")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
value = decodedResponse.value
}
} catch {
print("Invalid data")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I used Postman to see the output and it's like this:
{
"odata.metadata": "",
"value": [
{
"BusStopCode": "01012",
"RoadName": "Victoria St",
"Description": "Hotel Grand Pacific",
"Latitude": 1.29684825487647,
"Longitude": 103.85253591654006
},
...
There is no ID, so i used let id = UUID()
and added an Identifiable property, but it doesn't seem to work as I have nothing shown on the Simulator, however when I use another data set (from iTunes) with an identifiable ID, it yields a list when the code is run.
CodePudding user response:
your struct for Result
is correct with the let id = UUID()
, it decodes the json data you provided without errors. I suspect the "response" from the server is not what you think (for example some error message). Try using
struct Result: Identifiable, Codable {
let id = UUID()
var BusStopCode: String? // <-- here
var RoadName: String? // <-- here
}
You can also use the CodingKeys
as mentioned, if you get really scared about the Xcode message.
Could you add print(String(data: data, encoding: .utf8))
just after
let (data, _) = try await URLSession.shared.data(from: url)
, and show us exactly what it prints.
This is the full code I used to test my answer and show that it works:
struct Response: Codable {
var value: [Result]
}
struct Result: Identifiable, Codable {
let id = UUID()
var BusStopCode: String?
var RoadName: String?
}
struct ContentView: View {
@State private var value = [Result]()
//var locations = [Locations]()
var body: some View {
List(value, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.BusStopCode ?? "no BusStopCode")
.font(.headline)
Text(item.RoadName ?? "no RoadName")
}
}
.task {
await loadData()
}
}
func loadData() async {
let json = """
{
"odata.metadata": "",
"value": [
{
"BusStopCode": "01012",
"RoadName": "Victoria St",
"Description": "Hotel Grand Pacific",
"Latitude": 1.29684825487647,
"Longitude": 103.85253591654006
}
]
}
"""
guard let url = URL(string: "google.com") //link replaced due to privacy reasons
else { print("Invalid URL")
return
}
do {
// let (data, _) = try await URLSession.shared.data(from: url)
// simulated server response, since the url is not provided
let data = json.data(using: .utf8)!
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
value = decodedResponse.value
}
} catch {
print("Invalid data: \(error)")
}
}
}
PS: your url should be https:
as per Apple requirements, not http:
unless you have set the appropriate NSAppTransportSecurity
in your Info.plist
CodePudding user response:
Two ways to solve this without needing to add an extra property.
Identifiable
If you have an existing property, like perhaps BusStopCode
, that is unique for each object then you can use that as your id
property.
Add an extension to conform to Identifiable
extension Result: Identifiable {
var id: String {
BusStopCode
}
}
Hashable
Another option is to conform to Hashable
if each object as a whole (the combination of the properties) is unique. I assume this should also work fine with your data.
Simply conform to Hashable
and the compiler does the rest.
extension Result: Hashable {}
You do have to change your List
though so you use self
as id
List(value, id: \.self) { item in
Apart from that I do think you should follow swifts naming standard and start property names with a lowercase letter and you should definitely not use forced unwrapped properties.
struct Result: Codable {
var busStopCode: String
var roadName: String
enum CodingKeys: String, CodingKey {
case busStopCode = "BusStopCode"
case roadName = "RoadName"
}
}