Thanks for the speedy reply!
With all the adjustments from your pointers so far this is the only error I’m getting:
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.
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 LocationInfo
s. 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.
Hello, I am revisiting this problem that I didn’t get solved. I keep getting this error on this image blow
ContentView.swift
import SwiftUI
struct ContentView: View {
@EnvironmentObject var model: ContentViewModel
var body: some View {
List(model.order) { pickup in
Text("\(pickup.name )")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(ContentViewModel())
}
}
ContentViewModel.swift
mport Foundation
class ContentViewModel: ObservableObject {
@Published var order = [Order]()
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([Order].self, from: jsonData)
self.order = 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([Order].self, from: data!)
DispatchQueue.main.async {
self.order = json
}
} catch {
print(error)
}
}
dataTask.resume()
}
}
Order.swift
import Foundation
struct Order: Codable {
//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: Codable {
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: Codable {
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://dispatch.goedash.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"
}
DataCollectionApp.swift
import SwiftUI
@main
struct DataCollectionApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(ContentViewModel())
}
}
}
What’s the disconnect and how do I fix it?
The error message is telling you that your Order
type needs to conform to the Identifiable
protocol. This is common in SwiftUI, as it’s how the system can tell that items are unique and distinct from one another when diffing the View
to see if rerendering is needed.
To make a type conform to Identifiable
, you need to provide an id
property that is guaranteed to be unique across all instances of the type, i.e., all Order
s in your case.
So find some property that is unique to each Order
and assign that as the id
property. Take a look at the JSON you are working with. Is there something that uniquely identifies each Order
? Soemthing like… deliveryID
, perhaps?
You have two choices here:
deliveryID
property to just id
. This would also require that you adjust the CodingKeys
enum like below and change anywhere else you use deliveryID
to use id
instead: enum CodingKeys: String, CodingKey {
case pickup, dropoff, orderDetails, pickupTime, quoteExternalReference, deliveryExternalReference, tip
case id = "deliveryId"
case statusUpdateURL = "statusUpdateUrl"
case dropoffTime, controlledContents, allowedVehicles, orderValue, brandName, currency
}
2.Provide a separate id
computed property in Order
that just passes through the existing deliveryID
property:
var id: String { deliveryID }
Either way, you will also need to tell the compiler that Order
conforms to Identifiable
, like this:
struct Order: Identifiable, Codable {
//...blah blah blah...
}
Which option you choose depends what you are doing in the rest of your code and how you are using the existing deliveryID
property.
In trying to solve the error I ran into another error. What can satisfy the from:___?
import Foundation
class ContentViewModel: ObservableObject {
@Published var order = Order(from: )
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(Order.self, from: jsonData)
self.order = 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(Order.self, from: data!)
DispatchQueue.main.async {
self.order = json
}
} catch {
print(error)
}
}
dataTask.resume()
}
}
That error in the debugger is not related to the error message Xcode is showing you on line 12.
The one in the debugger is a runtime error that shows when some JSON you are trying to decode doesn’t match the format you say it should have. It’s probably left over from a previous run.
The error Xcode is showing you on line 12 is a compiler error you get because you are trying to call an initializer for Order
that does not exist. At least in the example code you previously posted, there is no method called init(from:)
.
I’m a little confused what you are trying to do now. Previously, your ContentViewModel
contained a published array of Order
items, now you just have a single Order
.
In order to do that, you need to either implement an init
method that creates an empty Order
itemm or make the order
property an Optional and default it to nil
.
I see the confusion. There was a tutorial that was helping me solve the error problem in the debugger that basically says that Im trying to connect within an array, but I have a dictionary. This…
@Published var order = Order(from: )
…was originally this:
@Published var order = [Order]()
and this…
let decodedData = try decoder.decode(Order.self, from: jsonData)
was originally this:
let decodedData = try decoder.decode([Order].self, from: jsonData)
You are right that it wasn’t matching up. I was wondering trying to find the missing piece of the puzzle.