Learn Courses My Dashboard

Displaying JSON data

Thanks for the speedy reply!

With all the adjustments from your pointers so far this is the only error I’m getting:

Do you still have a type called Pickup declared somewhere?

Just so we are on the same page. Here is what I have:

ContentViewModel.swift

import Foundation

class ContentViewModel: ObservableObject {
    @Published var accept = [OrderAccepted]()

    init() {
        fetchRemoteData()
    }

    func fetchLocalData() {

        let jsonUrl = Bundle.main.url(forResource: "data", withExtension: "json")
        
        do {
            //  Read the file into a data object
            let jsonData = try Data(contentsOf: jsonUrl!)
            
            let decoder = JSONDecoder()
            do {
                let decodedData = try decoder.decode([OrderAccepted].self, from: jsonData)
                self.accept = decodedData
            } catch {
                print("Unable to decode JSON data \(error.localizedDescription)")
            }
        } catch {
            print("Unable to find JSON data")
        }
    }

    func fetchRemoteData() {
        let urlString = "https://www.goedash.com/_functions/api/accept" //http://d1058276.myweb.iinethosting.net.au/data.json
        let url = URL(string: urlString)
        let defaultSession = URLSession(configuration: .default)
        let dataTask = defaultSession.dataTask(with: url!) { data, response, error in

            if error != nil {
                print(error!)
                return
            }

            do {
                let json = try JSONDecoder().decode([OrderAccepted].self, from: data!)
                DispatchQueue.main.async {
                    self.accept = json
                }
            } catch {
                print(error)
            }
        }
        dataTask.resume()
    }
}

OrderAccepted.swift

import Foundation

struct OrderAccepted: Decodable {
    //although what kind of name is Accept?
    
    let pickup, dropoff: LocationInfo
    
    let orderDetails: [OrderDetail]

    let pickupTime: String
    let quoteExternalReference: String
    let deliveryExternalReference: String
    let tip: Double
    let deliveryID: String
    let statusUpdateURL: String
    let dropoffTime: String
    let controlledContents: String
    let allowedVehicles: String
    let orderValue: Double
    let brandName: String
    let currency: String

    enum CodingKeys: String, CodingKey {
        case pickup, dropoff, orderDetails, pickupTime, quoteExternalReference, deliveryExternalReference, tip
        case deliveryID = "deliveryId"
        case statusUpdateURL = "statusUpdateUrl"
        case dropoffTime, controlledContents, allowedVehicles, orderValue, brandName, currency
    }
}

struct LocationInfo: Decodable, Identifiable {
    
    let id: UUID
    let name: String
    let phoneNumber: String
    let street: String
    let city: String
    let state: String
    let postalCode: String
    let country: String
    let latitude: Double
    let longitude: Double
    let unit: String
    let instructions: String
}

struct OrderDetail: Decodable {
    let title: String
    let quantity: Int
}

data.json

{
  "pickup": {
    "id": "7ad33209-1223-449b-afbc-c69d20935389",
    "name": "Kermit's Salads",
    "phoneNumber": "+12125551234",
    "street": "26 Broadway",
    "city": "New York City",
    "state": "NY",
    "postalCode": "10004",
    "country": "US",
    "latitude": 40.716038,
    "longitude": -74.00631,
    "unit": "104B",
    "instructions": "Use back entrance"
  },
  "dropoff": {
    "id": "7ad33209-1223-449b-afbc-c69d20935389",
    "name": "Miss Piggy",
    "phoneNumber": "+12125555678",
    "street": "312 Broadway",
    "city": "New York City",
    "state": "NY",
    "postalCode": "10004",
    "country": "US",
    "latitude": 40.24377,
    "longitude": -74.10277,
    "unit": "Suite 300",
    "instructions": "Leave with security guard"
  },
  "orderDetails": [
    {
      "title": "Salad Green",
      "quantity": 3
    }
  ],
  "pickupTime": "2015-09-22T18:30:00.0000000Z",
  "quoteExternalReference": "basket_e699aece",
  "deliveryExternalReference": "order_713a8bd9",
  "tip": 3.5,
  "deliveryId": "cb1915b1-3330-4477-b4bf-88c9b935943c",
  "statusUpdateUrl": "https://mywebsite.com/v1",
  "dropoffTime": "2015-09-22T18:30:00.0000000Z",
  "controlledContents": "Alcohol,Tobacco",
  "allowedVehicles": "Walker,Bicycle,DeliveryBicycle,Car,Van",
  "orderValue": 22.5,
  "brandName": "BigBellyBurger",
  "currency": "USD"
}

Hmm, don’t see anything called Pickup there. Might be time to clean your build folder.

Cleaning totally worked! Just one more thing. How do I correct this:

contentView.swift

import SwiftUI

struct ContentView: View {
    @EnvironmentObject var model: ContentViewModel

    var body: some View {
        List(model.accept) { pickup in
            
            Group {
                
            Text("\(pickup.id)")
            Text("\(pickup.name)")
            Text("\(pickup.phoneNumber)")
            Text("\(pickup.street)")
            Text("\(pickup.city)")
            Text("\(pickup.state)")
                
            }
            
            Text("\(pickup.postalCode)")
            Text("\(pickup.country)")
            Text("\(pickup.latitude)")
            Text("\(pickup.longitude)")
            Text("\(pickup.unit)")
            Text("\(pickup.instructions)")
            
        }
    }
}

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

You need to give OrderAccepted an id property so that SwiftUI has a way to distinguish between different orders.

Probably the easiest way is to just add this:

var id: String { deliveryID }

This uses a computed property to simply pass through the deliveryID property decoded from the JSON response. You can use another property if you prefer, though.

1 Like

This is a puzzle. How would I implement:

var id: String { deliveryID }
import Foundation

struct OrderAccepted: Decodable, Identifiable {
    
    var id: String { deliveryID }
    
    var pickup, dropoff: LocationInfo
    
    let orderDetails: [OrderDetail]

    let pickupTime: String
    let quoteExternalReference: String
    let deliveryExternalReference: String
    let tip: Double
    let deliveryID: String
    let statusUpdateURL: String
    let dropoffTime: String
    let controlledContents: String
    let allowedVehicles: String
    let orderValue: Double
    let brandName: String
    let currency: String

    enum CodingKeys: String, CodingKey {
        case pickup, dropoff, orderDetails, pickupTime, quoteExternalReference, deliveryExternalReference, tip
        case deliveryID = "deliveryId"
        case statusUpdateURL = "statusUpdateUrl"
        case dropoffTime, controlledContents, allowedVehicles, orderValue, brandName, currency
    }
}

struct LocationInfo: Decodable, Identifiable {
    
    let id: UUID
    let name: String
    let phoneNumber: String
    let street: String
    let city: String
    let state: String
    let postalCode: String
    let country: String
    let latitude: Double
    let longitude: Double
    let unit: String
    let instructions: String
}

struct OrderDetail: Decodable {
    let title: String
    let quantity: Int
}


…in my contentView:

import SwiftUI

struct ContentView: View {
    @EnvironmentObject var model: ContentViewModel

    var body: some View {
        List(model.accept) { locationinfo in
            
            Group {
                
            Text("\(locationinfo.id)")
            Text("\(locationinfo.name)")
            Text("\(locationinfo.phoneNumber)")
            Text("\(locationinfo.street)")
            Text("\(locationinfo.city)")
            Text("\(locationinfo.state)")
                
            }
            
            Text("\(locationinfo.postalCode)")
            Text("\(locationinfo.country)")
            Text("\(locationinfo.latitude)")
            Text("\(locationinfo.longitude)")
            Text("\(locationinfo.unit)")
            Text("\(locationinfo.instructions)")
            
        }
    }
}

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

You don’t need to do anything in ContentView. Making OrderAccepted conform to the Identifiable protocol satisfies the requirement on ForEach that was causing the error message.

Unless you are getting other errors you should be good to go.

Although, hang on… right after hitting Reply I noticed that you are expecting to receive a LocationInfo struct in the ForEach but you will not. Your ContentViewModel.accept property is an array of OrderAccepted structs, each of which has a pickup and dropoff property. Those properties are LocationInfos. So you need to correctly drill down to get them from the OrderAccepted items you are being given by the ForEach.

I’m away from home on my iPad right now so I can’t give you a code example but I will work something up tonight to illustrate if you haven’t got it working by then.

1 Like