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