Converting JSON 64bit string to an Image - difficulty - json

My project accesses a JSON file with 200+ country elements of which one name/value pair is flag: containing what I think is Base64 encryption - example below. I have successfully included the name: and the :flag in a Picker. The Picker works fine and displays the chosen country and update the CoreData & CloudKit (ClientEntity) record with both. The challenge is to call the record for display or editing. I cannot get the flag Image to display - only the default value. It is storing something in the clientFlag field. I was expecting it to store the flag:1920 bytes of JSON data - instead (copied from the console) it is storing "CD_clientFlag" = "{ length=1458, sha256=c1572719d906e1df50fdf296845b0942ef148060c609d585434777b6276a67e9 }";\n
Clearly, my logic is faulty or this field contains a different data format, or both. Any help would be appreciated.
Of course a better method would be to reference the JSON data via the id: or the name:, but I gave up on this due to inexperience and opted to store both name: and flag
Relevant code below:
Successful Picker code in a View
NavigationStack {
Form {
VStack(alignment: .leading) {
clientNameAddress
Group {
Picker(selection: $selectedCountry,
label: Text("")) {
ForEach(countryVM.countries) { country in
HStack {
Text(country.name)
if let flagImage = country.flagImage {
Image(uiImage: flagImage)
} else {
Image(systemName: "questionmark")
}
}.tag(country as Country?)//: HSTACK6
}
}
.padding(.horizontal, 10)
.pickerStyle(MenuPickerStyle())
VStack {
Text("Selected Country: \(selectedCountry?.name ?? "No Name")")
.foregroundColor(.secondary)
if let flagImage = selectedCountry?.flagImage {
Image(uiImage: flagImage)
}
}.padding(.horizontal, 10)
}
Toggle("Existing Client?", isOn: $clientVM.clientExisting)
.toggleStyle(.switch)
.foregroundColor(.blue)
.padding(.horizontal, 10)
// SelectContact()
Button(action: {
assignFlag()
print("AddView \(clientVM.clientHostCountry)")
clientVM.addClient(context: viewContext)
dismiss()
}, label: {
if clientVM.clientItem == nil {
Text("Add Account")
.frame(minWidth: 0, maxWidth: 200)
} else {
Text("Save Modified Client")
.frame(minWidth: 0, maxWidth: 200)
}
})
.tint(.purple)
.buttonStyle(.bordered)
.buttonBorderShape(.roundedRectangle)
}
.navigationTitle(clientVM.clientItem == nil ? "Add Account" : "Edit Account \(clientVM.clientName)")
}.disableAutocorrection(true)
#if os(iOS)
.navigationBarTitle("Add Account")
.navigationBarTitleDisplayMode(.inline)
#endif
}
JSON Example
{
"id": 2,
"name": "Albania",
"isoAlpha2": "AL",
"isoAlpha3": "ALB",
"isoNumeric": 8,
"currency": {
"code": "ALL",
"name": "Lek",
"symbol": "Lek"
},
"flag": "iVBORw0KGgoAAAANSUhEUgAAAB4AAAAUCAIAAAAVyRqTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyRDFBRDI0NTE3NkYxMUUyODY3Q0FBOTFCQzlGNjlDRiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoyRDFBRDI0NjE3NkYxMUUyODY3Q0FBOTFCQzlGNjlDRiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjdERDBDNDA4MTc1MzExRTI4NjdDQUE5MUJDOUY2OUNGIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjJEMUFEMjQ0MTc2RjExRTI4NjdDQUE5MUJDOUY2OUNGIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+GPq/agAAAiRJREFUeNrEVb9rFEEUfm9m9nb3bhNz50UMClopRAsFrUURW1tBrSzsLPwfbPwDbGz8F8QiIkLAKiCkUIKiGBEFwXAhd7fZH7Mz83zZtbC4TdyF4LDF8N7ON9/73jczuN4/A4czBBzaqIUmAA+Q0wjQRzkUCsv4USEHKKs4/0DtWOdAgxLxrUk+mqyHIkLx2eg1k1gA3kwDtYFeFOqVnj5NRwXQip7eGG9+svlPV1wff3mejwuiZ9n2i3zCRWANAta1kaFX9OS1jkdkHdGyCt6blMmel8E3p1OgY6iueL2b/pEtZ5qx5kRCLIhMyK4WMQFt2HzdpEzypZ5OnOVUSoT1gqi6BOvA7ZoDUan5JB3BXxPeOALBahigxloLQO4SFy5hBjMOpuA0zc4ebL4OYExuZl0dxNiRh63MZ4jYXjzJiG77/cuqW8UvqvBO0Ge+jjsplKHmgrCIIeICyke9pXPKZ+kvqPCS1+X6T4vO42iJN/YB22jNIo6cYWN9dfqdya560TxKruKaF32w2abVW2VWtNCa6fRQnpTeD1vcD4anZOdNEa8VCZN9EA6/2+KE9Ob3dUit+XbJHRfqXjBgTZjYhk3nUDAQN/CsDJbDYIfcbvlhU+hqQUpuSo6tcstfYMp8q9z1+7+cyfZMuUe4zZGp/GfLxRm4bbIPu4scYbIJOO6EO+hSVf9y8zLQmGxUKrNDRu7HtSH0n+NHrpr8/1fmtwADAEjB+xzEjgF0AAAAAElFTkSuQmCC"
}
CountryViewModel
import SwiftUI
import Foundation
struct Country: Codable, Identifiable, Comparable, Hashable {
let id = UUID() // New
let name: String
let isoAlpha3: String
let flag: Data?
enum CodingKeys: String, CodingKey {
case name, isoAlpha3, flag
}
var flagImage: UIImage? {
guard let flagData = flag else { return nil }
return UIImage(data: flagData)
}
static func == (lhs: Country, rhs: Country) -> Bool {
lhs.name == rhs.name
}
static func < (lhs: Country, rhs: Country) -> Bool {
lhs.name < rhs.name
}
struct HeadingData: Codable, Identifiable, Hashable {
let id: Int
let name: String
let isoAlpha3: String
let flag: Data
}
struct CurrencyData: Codable, Equatable, Identifiable, Hashable {
let id: UUID
let code: String
let name: String
let symbol: String
}
}
class CountryModel: ObservableObject {
#Published var countries: [Country] = []
init() {
self.countries = Bundle.main.decode("Countries.json")
// let data = self.countries
// let json = try? JSONSerialization.jsonObject(with: data, options: [])
}
}
Computed Property to decode the clientFlag
extension ClientEntity {
var showFlag: Image {
let str = clientFlag ?? Data()
if str.isEmpty {
let flagImage = Image(systemName: "plus.circle")
return flagImage
} else {
let dataDecoded : Data = Data(base64Encoded: str) ?? Data()
guard let flagImage = UIImage.init(data: dataDecoded) else { return Image(systemName: "plus.circle") }
return Image(uiImage: flagImage)
}
}
}
ClientViewModel
import Foundation
import Combine
import CoreData
class ClientViewModel: ObservableObject {
#Published var clientName = ""
#Published var clientAddress1 = ""
#Published var clientAddress2 = ""
#Published var clientPhoneNumber = ""
#Published var clientHostCountry = ""
#Published var clientFlag: Data?
#Published var clientIndustry = ""
#Published var clientLogo: Data?
#Published var clientComments = ""
#Published var clientExisting = false
#Published var clientWebSite: URL?
#Published var clientUpdated: Date = Date()
#Published var clientCreated: Date = Date()
#Published var clientItem: ClientEntity!
#Published var selectedContact: [ContactEntity] = []
func addClient(context: NSManagedObjectContext) {
if clientItem == nil {
let client = ClientEntity(context: context)
client.clientName = clientName
client.clientAddress1 = clientAddress1
client.clientAddress2 = clientAddress2
client.clientPhoneNumber = clientPhoneNumber
print("clientVM \(clientHostCountry)")
client.clientHostCountry = clientHostCountry
client.clientFlag = clientFlag
client.clientIndustry = clientIndustry
// client.clientLogo = clientLogo
client.clientComments = clientComments
client.clientExisting = clientExisting
// client.clientWebSite = clientWebSite
client.clientUpdated = clientUpdated
client.clientCreated = clientCreated
print("Selected Contact: \(selectedContact.description)")
let uniqueContact = Set(selectedContact)
for contact in uniqueContact {
client.addToContacts(contact)
}
} else {
clientItem.clientName = clientName
clientItem.clientAddress1 = clientAddress1
clientItem.clientAddress2 = clientAddress2
clientItem.clientPhoneNumber = clientPhoneNumber
clientItem.clientHostCountry = clientHostCountry
clientItem.clientFlag = clientFlag
clientItem.clientIndustry = clientIndustry
// clientItem.clientLogo = clientLogo
clientItem.clientComments = clientComments
clientItem.clientExisting = clientExisting
// clientItem.clientWebSite = clientWebSite
clientItem.clientUpdated = clientUpdated
clientItem.clientCreated = clientCreated
print("Selected Contact: \(selectedContact.description)")
let uniqueContact = Set(selectedContact)
for contact in uniqueContact {
clientItem.addToContacts(contact)
}
}
save(context: context)
clientName = ""
clientAddress1 = ""
clientAddress2 = ""
clientPhoneNumber = ""
clientHostCountry = ""
clientFlag = Data()
clientIndustry = ""
// clientLogo = Data()
clientComments = ""
clientExisting = false
// clientWebSite = URL(string: "")!
clientUpdated = Date()
clientCreated = Date()
}
func editClient(client: ClientEntity) {
clientItem = client
}
func delete(client: ClientEntity, context: NSManagedObjectContext) {
context.delete(client)
save(context: context)
}
func save(context: NSManagedObjectContext) {
do {
try context.save()
} catch {
fatalError("Error saving: \(error.localizedDescription)")
}
}
func isExisting(client: ClientEntity, context: NSManagedObjectContext) {
client.clientExisting.toggle()
save(context: context)
}
}
I tried adding the .jpeg and .png parameters as well as the reverse of the successful Picker code. No luck.
New Issue
The program crashes with Thread 1: breakpoint 1.2 (1) error (green background?) at the .tag(country as Country) in the Picker. Hovering over the 'country' property shows that it contains the 1st country in the JSON file. The Variables window shows:
country B2Bv5.Country
id Foundation.UUID
uuid (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
name String
_guts _StringGuts
_object _StringObject
_countAndFlagsBits UInt64 13835058055282163723
_object Builtin.BridgeObject 0x4000600001ef0ec0
isoAlpha3 String
_guts _StringGuts
_object _StringObject
_countAndFlagsBits UInt64 4671041
_object NSTaggedPointerString "RCp1xmeaxi" 0xe300000000000000
flag String? some
_guts _StringGuts
_object _StringObject
_countAndFlagsBits UInt64 13835058055282165492
_object Builtin.BridgeObject 0x4000000128065000
CountryViewModel
import SwiftUI
struct Country: Codable, Identifiable, Comparable, Hashable {
let id = UUID() // New
let name: String
let isoAlpha3: String
let flag: String?
enum CodingKeys: String, CodingKey {
case name, isoAlpha3, flag
}
var flagImage: UIImage? {
if let flagString = flag,
let data = flagString.data(using: .utf8),
let decodedData = Data(base64Encoded: data),
let image = UIImage(data: decodedData) {
return image
} else {
let img = UIImage(systemName: "plus.circle")
return img == nil ? UIImage() : img!
}
}
static func == (lhs: Country, rhs: Country) -> Bool {
lhs.name == rhs.name
}
static func < (lhs: Country, rhs: Country) -> Bool {
lhs.name < rhs.name
}
struct HeadingData: Codable, Identifiable, Hashable {
let id: Int
let name: String
let isoAlpha3: String
let flag: String
}
struct CurrencyData: Codable, Equatable, Identifiable, Hashable {
let id: UUID
let code: String
let name: String
let symbol: String
}
}
class CountryModel: ObservableObject {
#Published var countries: [Country] = []
init() {
self.countries = Bundle.main.decode("Countries.json")
}
}
Picker extension
var hostCountry: some View {
VStack(alignment: .leading) {
Picker(selection: $clientVM.clientHostCountry,
label: Text("")) {
ForEach(countryVM.countries) { country in
VStack {
Text(country.name)
Image(uiImage: country.flagImage!)
}.tag(country as Country)
}
.padding(.horizontal, 10)
.padding()
.pickerStyle(MenuPickerStyle())
}.padding(.horizontal)
}
extension ClientEntity
extension ClientEntity {
var showName: String {
return clientName ?? "Undefined"
}
}
extension ClientEntity {
var showExisting: String {
if clientExisting {
return " Client"
} else {
return "New Logo"
}
}
}

You could try this simple approach, to display the Image of the flag, works for me:
As can be seen in the json data, flag is a String.
So make sure you declare let flag: String? in your struct Country, so you can decode the json in your CountryModel.
struct ContentView: View {
let flag = "iVBORw0KGgoAAAANSUhEUgAAAB4AAAAUCAIAAAAVyRqTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyRDFBRDI0NTE3NkYxMUUyODY3Q0FBOTFCQzlGNjlDRiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoyRDFBRDI0NjE3NkYxMUUyODY3Q0FBOTFCQzlGNjlDRiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjdERDBDNDA4MTc1MzExRTI4NjdDQUE5MUJDOUY2OUNGIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjJEMUFEMjQ0MTc2RjExRTI4NjdDQUE5MUJDOUY2OUNGIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+GPq/agAAAiRJREFUeNrEVb9rFEEUfm9m9nb3bhNz50UMClopRAsFrUURW1tBrSzsLPwfbPwDbGz8F8QiIkLAKiCkUIKiGBEFwXAhd7fZH7Mz83zZtbC4TdyF4LDF8N7ON9/73jczuN4/A4czBBzaqIUmAA+Q0wjQRzkUCsv4USEHKKs4/0DtWOdAgxLxrUk+mqyHIkLx2eg1k1gA3kwDtYFeFOqVnj5NRwXQip7eGG9+svlPV1wff3mejwuiZ9n2i3zCRWANAta1kaFX9OS1jkdkHdGyCt6blMmel8E3p1OgY6iueL2b/pEtZ5qx5kRCLIhMyK4WMQFt2HzdpEzypZ5OnOVUSoT1gqi6BOvA7ZoDUan5JB3BXxPeOALBahigxloLQO4SFy5hBjMOpuA0zc4ebL4OYExuZl0dxNiRh63MZ4jYXjzJiG77/cuqW8UvqvBO0Ge+jjsplKHmgrCIIeICyke9pXPKZ+kvqPCS1+X6T4vO42iJN/YB22jNIo6cYWN9dfqdya560TxKruKaF32w2abVW2VWtNCa6fRQnpTeD1vcD4anZOdNEa8VCZN9EA6/2+KE9Ob3dUit+XbJHRfqXjBgTZjYhk3nUDAQN/CsDJbDYIfcbvlhU+hqQUpuSo6tcstfYMp8q9z1+7+cyfZMuUe4zZGp/GfLxRm4bbIPu4scYbIJOO6EO+hSVf9y8zLQmGxUKrNDRu7HtSH0n+NHrpr8/1fmtwADAEjB+xzEjgF0AAAAAElFTkSuQmCC"
#State var img = UIImage()
var body: some View {
Image(uiImage: img)
.onAppear {
if let data = flag.data(using: .utf8),
let decodedData = Data(base64Encoded: data),
let image = UIImage(data: decodedData) {
img = image
}
}
}
}
EDIT-1
In your model struct Country you may want to use the following code:
struct Country: Codable, Identifiable, Comparable, Hashable {
let id = UUID()
let name: String
let isoAlpha3: String
let flag: String? // <--- here
enum CodingKeys: String, CodingKey {
case name, isoAlpha3, flag
}
// --- here
var flagImage: UIImage? {
if let flagString = flag,
let data = flagString.data(using: .utf8),
let decodedData = Data(base64Encoded: data),
let image = UIImage(data: decodedData) {
return image
} else {
return nil
}
}
//....
}
Once you have your image as Data, you can use that to store it in your clientFlag in your ClientEntity and your ClientViewModel things.
EDIT-2:
In your Picker,
instead of using this if let data = country.flag!.data(using: .utf8),...etc
trying to again decode the flag,
why not use the country.flagImage, this is what you have it for. Modify struct Country with:
var flagImage: UIImage {
if let flagString = flag,
let data = flagString.data(using: .utf8),
let decodedData = Data(base64Encoded: data),
let image = UIImage(data: decodedData) {
return image
} else {
let img = UIImage(systemName: "plus.circle")
return img == nil ? UIImage() : img!
}
}
and in the Picker use:
VStack {
Text(country.name)
Image(uiImage: country.flagImage)
}

Your flag image string iVBORw0KGgoAAAANSUhEUgAAAB4AAAAUCAIAAAAVyRqTAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw is almost base64, but not quite - it's missing the padding characters at the end that Data(base64encoded:) needs.
You can easily add them like this:
let str = "iVBOR...AADw" // your clientFlag string from JSON
let pad = String(repeating: "=", count: 4 - (str.count % 4))
let data = Data(base64Encoded: str + pad)

Related

How to return object from nested json

I have nested JSON and I'm trying to return an object and update UI. However, I can access properties only from Result array, but can't reach Step from AnalyzedInstactions.
Here are classes that represent data in the JSON. Generated it with https://app.quicktype.io
import UIKit
class ResultArray: Codable {
var results = [Result]()
}
class Result: Codable, CustomStringConvertible {
var title: String = ""
var image = ""
var readyInMinutes: Int? = 0
var servings: Int? = 0
var cuisines = [String]()
var dishTypes = [String]()
var diets = [String]()
var occasions = [String]()
var analyzedInstructions = [AnalyzedInstruction]()
var description: String {
return "\nResults - Name: \(title), Summary: \(String(describing: readyInMinutes ?? nil)), \(String(describing: servings ?? nil)) "
}
}
// MARK: - Steps
class AnalyzedInstruction: Codable {
var name: String? = ""
var steps = [Step]()
}
class Step: Codable {
var number: Int = 0
var step: String = ""
}
Here is my parse method
private func parse(data: Data) -> [Result] {
do {
let decoder = JSONDecoder()
let result = try decoder.decode(ResultArray.self, from: data)
return result.results
} catch {
print("JSON Error: \(error)")
return []
}
}
try this simple code to access your steps from AnalyzedInstructions:
let results: [Result] = parse(data: theData)
if let firstResult = results.first {
if let firstAnaInst = firstResult.analyzedInstructions.first {
for step in firstAnaInst.steps {
print("--> step.step: \(step.step) step.number: \(step.number)")
}
} else { print(" NO analyzedInstructions") }
} else { print(" NO results") }
if you want all steps:
for result in results {
for anaInst in result.analyzedInstructions {
for step in anaInst.steps {
print("--> step.step: \(step.step) step.number: \(step.number)")
}
}
}
PS: if appropriate, you may consider using struct instead of class for your json models.

How to solve error: 'Missing arguments for parameters 'data', 'detailedData' in call'

I'm trying to make a detailed view of a contact from a contacts list. This is the WIP of that view. I am getting the error;
Missing arguments for parameters 'data', 'detailedData' in call
on the line with ContactDetail() in the ContactDetail_Previews struct.
I think I understand that this is because something is missing from the variables data and detailedData, but my confusion comes from how I use similar code for the actual list view of all the contacts, with no such error. I have pasted the code for the whole list view below the code for the detailed view.
Any help would be appreciated!
Contact Detail Code:
import SwiftUI
struct ContactDetail: View {
var data: Response_Detailed.Contact_Detailed
#ObservedObject var detailedData: getData_Detailed
var body: some View {
VStack {
Text(data.first_name + " " + data.last_name)
Text(data.phone_number)
Text(data.birthday)
Text(data.address)
Text(data.updated_date)
Text(data.create_date)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.detailedData.updateDetailed_Data()
}
}
}
}
}
class getData_Detailed: ObservableObject {
#Published var data = [Response_Detailed.Contact_Detailed]()
#Published var id = 1
init() {
updateDetailed_Data()
}
func updateDetailed_Data() {
let url = "DATABASE_LINK\(id)"
let session = URLSession(configuration: .default)
session.dataTask(with: URL(string: url)!) { (data, _, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
do {
let json = try JSONDecoder().decode(Response_Detailed.self, from: data!)
let oldData = self.data
DispatchQueue.main.async {
self.data = oldData + json.data
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try! encoder.encode(json)
print(String(data: data, encoding: .utf8)!)
}
}
catch {
print(error.localizedDescription)
}
}.resume()
}
}
struct ContactDetail_Previews: PreviewProvider {
static var previews: some View {
ContactDetail()
}
}
struct Response_Detailed: Codable {
struct Contact_Detailed: Codable, Identifiable {
public let id: Int
public let first_name: String
public let last_name: String
public let birthday: String
public let phone_number: String
public let create_date: String
public let updated_date: String
public let address: String
}
public let data: [Contact_Detailed]
enum CodingKeys : String, CodingKey {
case data = "data"
}
}
Contacts List View Code: (Note the same error comes up on the line with NavigationLink.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
HStack {
ContactsList()
.navigationBarTitle("Contacts")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Image(systemName: "plus")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20.0)
}
}
}
}
}
}
struct ContactsList: View {
#ObservedObject var listData = getData()
var body: some View {
List(0..<listData.data.count, id: \.self) {i in
NavigationLink(destination: ContactDetail()) {
if i == self.listData.data.count - 1 {
cellView(data: self.listData.data[i], isLast: true, listData: self.listData)
}
else {
cellView(data: self.listData.data[i], isLast: false, listData: self.listData)
}
}
}
}
}
struct cellView: View {
var data: Response.Contact
var isLast: Bool
#ObservedObject var listData: getData
var body: some View {
VStack(alignment: .leading, spacing: 12) {
if self.isLast {
Text(data.first_name + " " + data.last_name)
.fontWeight(/*#START_MENU_TOKEN#*/.bold/*#END_MENU_TOKEN#*/)
.font(.title2)
.padding(/*#START_MENU_TOKEN#*/[.leading, .bottom, .trailing], 5.0/*#END_MENU_TOKEN#*/)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.listData.updateData()
}
}
}
else {
Text(data.first_name + " " + data.last_name)
.fontWeight(/*#START_MENU_TOKEN#*/.bold/*#END_MENU_TOKEN#*/)
.font(.title2)
.padding(/*#START_MENU_TOKEN#*/[.leading, .bottom, .trailing], 5.0/*#END_MENU_TOKEN#*/)
}
}
.padding(.top, 10)
}
}
class getData: ObservableObject {
#Published var data = [Response.Contact]()
#Published var limit = 15
#Published var skip = 0
init() {
updateData()
}
func updateData() {
let url = "DATABASE_LINK?skip=\(skip)&limit=\(limit)"
let session = URLSession(configuration: .default)
session.dataTask(with: URL(string: url)!) { (data, _, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
do {
let json = try JSONDecoder().decode(Response.self, from: data!)
let oldData = self.data
DispatchQueue.main.async {
self.data = oldData + json.data
self.limit += 15
self.skip += 15
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try! encoder.encode(json)
print(String(data: data, encoding: .utf8)!)
}
}
catch {
print(error.localizedDescription)
}
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Response: Codable {
struct Contact: Codable, Identifiable {
public let id: Int
public let first_name: String
public let last_name: String
public let updated_date: String
}
struct Pagination_Data: Codable {
public let skip: Int
public let limit: Int
public let total: Int
}
public let data: [Contact]
public let pagination: Pagination_Data
enum CodingKeys : String, CodingKey {
case data = "data"
case pagination = "pagination"
}
}
Your ContactsList is giving the only variable an initial value.
#ObservedObject var listData = getData()
Not IAW Apple documentation but the = getData() is the initial value
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
BTW you should change it to
#StateObject var listData = getData()
Your ContactDetail View has two variables without an initial value
struct ContactDetail: View {
var data: Response_Detailed.Contact_Detailed
#ObservedObject var detailedData: getData_Detailed
No = sign so a struct creates an init that looks like this
init(data: Response_Detailed.Contact_Detailed, detailedData: getData_Detailed)
So in your Preview you need to provide the initial values
ContactDetail(data: /.../, detailedData: /.../)
The /.../ symbolizes where you will provide sample data

Why is my API call not completely reached?

I am currently building a public transportation app in SwiftUI Xcode 12 beta, where you input origin, destination, time of arrival, and date. The idea is to then convert the origin and destination input (private func FetchOriginCoordinates and private func FetchDestCoordinates) into coordinates using an API. This API will then give that data to another API that plans the route.
The problem is that the route planner API doesn't give any output. I have tried printing self.Trips.append(...) but the list is completely empty. I believe this is because the code doesn't reach that part of the code. I have put some print statements in the code in order to understand where the error is occurring. When I call the API my output in the console is this:
Test 2021-04-16 22:33:19.271246+0200 OnTrack2.0[6592:428359] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed Optional(http://api.sl.se/api2/TravelplannerV3_1/trip.json?key=40892db48b394d3a86b2439f9f3800fd&originExtId=300101115&destExtId=300101426&Date=2021-04-18&Time=09:20&searchForArrival=1) [] 10767 bytes Level 1 Level 1.5
This means that the code only reaches the print("Level 1.5") statement. But never the following print statements such as print("Level 2") or print(self.trips). Why is that and how do I solve it?
import Foundation
struct NominationStructure: Decodable {
var lat: String
var lon: String
enum CodingKeys: String, CodingKey {
case lat = "lat"
case lon = "lon"
}
}
struct NearbyStopsStructure: Decodable {
var stopLocationOrCoordLocation: [stopLocationOrCoordLocationStructure]
}
struct stopLocationOrCoordLocationStructure: Decodable {
var StopLocation: StopLocationStructure
}
struct StopLocationStructure: Decodable {
var mainMastExtId: String
}
struct JSONStructure: Decodable {
var Trip: [TripStructure]
}
struct TripStructure: Decodable {
var LegList: LegListStructure
}
struct LegListStructure: Decodable {
var Leg: [LegStructure]
}
struct LegStructure: Decodable {
var Origin: StationStructure
var Destination: StationStructure
var Product: ProductStructure
var name: String
var type: String
var dist: String
}
struct StationStructure: Decodable {
var time: String
var name: String
var date: String
}
struct ProductStructure: Decodable {
var catIn: String
}
struct LocationInfo {
var iD = String()
var input = String()
var lat = String()
var lon = String()
var name = String()
var time = String()
var date = String()
var vehicleType = String()
var transportType = String()
var dist = String()
var legName = String()
}
//One way is to call your functions when you know a step is completed
enum TripFetchStatus: String {
case start
case fetchedOriginCoordinates
case fetchedOriginId
case fetchedDestinationCoordinates
case fetchedDestinationId
case fetchingTrip
case done
case stopped
}
class PlanTripViewModel: ObservableObject {
//Apple frowns upon frezzing a screen so having a way to update the user on what is going on
//When a step is complete have it do something else
#Published var fetchStatus: TripFetchStatus = .stopped{
didSet{
switch fetchStatus {
case .start:
print("Test")
FetchOriginCoordinates { (cors) in
self.origin.lat = cors[0].lat
self.origin.lon = cors[0].lon
self.fetchStatus = .fetchedOriginCoordinates
}
case .fetchedOriginCoordinates:
self.FetchOriginID { (stops) in
self.origin.iD = stops.stopLocationOrCoordLocation[0].StopLocation.mainMastExtId
self.fetchStatus = .fetchedOriginId
}
case .fetchedOriginId:
FetchDestCoordinates { (cors) in
self.dest.lat = cors[0].lat
self.dest.lon = cors[0].lon
self.fetchStatus = .fetchedDestinationCoordinates
}
case .fetchedDestinationCoordinates:
self.FetchDestID { (stops) in
self.dest.iD = stops.stopLocationOrCoordLocation[0].StopLocation.mainMastExtId
self.fetchStatus = .fetchedDestinationId
}
case .fetchedDestinationId:
//Once you have everthing in place then go to the next API
FetchTrip()
case .fetchingTrip:
print("almost done")
case .done:
print("any other code you need to do")
case .stopped:
print("just a filler")
}
}
}
#Published var trip: LocationInfo = LocationInfo()
#Published var dest: LocationInfo = LocationInfo()
#Published var origin: LocationInfo = LocationInfo()
#Published var arrivalTime = String()
#Published var travelDate = String()
#Published var tripIndex = Int()
#Published var Trips: [Dictionary<String, String>] = []
public func FetchTrip() {
Trips.removeAll()
let tripKey = "40892db48b394d3a86b2439f9f3800fd"
let tripUrl = URL(string: "http://api.sl.se/api2/TravelplannerV3_1/trip.json?key=\(tripKey)&originExtId=\(self.origin.iD)&destExtId=\(self.dest.iD)&Date=\(self.travelDate)&Time=\(self.arrivalTime)&searchForArrival=1")
print(tripUrl)
URLSession.shared.dataTask(with: tripUrl!) {data, response, error in
if let data = data {
print(data)
print("Level 1")
do {
print("Level 1.5")
if let decodedJson = try? JSONDecoder().decode(JSONStructure.self, from: data) {
self.tripIndex = decodedJson.Trip.count - 1
print("Level 2")
for i in 0..<decodedJson.Trip[self.tripIndex].LegList.Leg.count {
self.trip.transportType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].type
if self.trip.transportType == "WALK" {
self.origin.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.name
self.origin.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.time.prefix(5))
self.origin.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.date
self.dest.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.name
self.dest.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.time.prefix(5))
self.dest.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.date
self.trip.vehicleType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Product.catIn
self.trip.dist = decodedJson.Trip[self.tripIndex].LegList.Leg[i].dist
print(self.origin.name)
print(self.dest.name)
self.Trips.append(["Origin": self.origin.name, "Destination": self.dest.name, "OriginTime": self.origin.time, "DestTime": self.dest.time, "OriginDate": self.origin.date, "DestDate": self.dest.date, "TransportType": self.trip.transportType, "VehicleType": self.trip.vehicleType, "Distance": self.trip.dist])
}
else {
self.origin.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.name
self.origin.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.time.prefix(5))
self.origin.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.date
self.dest.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.name
self.dest.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.time.prefix(5))
self.dest.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.date
self.trip.vehicleType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Product.catIn
self.trip.legName = decodedJson.Trip[self.tripIndex].LegList.Leg[i].name
print(self.origin.name)
print(self.dest.name)
self.Trips.append(["Origin": self.origin.name, "Destination": self.dest.name, "OriginTime": self.origin.time, "DestTime": self.dest.time, "OriginDate": self.origin.date, "DestDate": self.dest.date, "TransportType": self.trip.transportType, "VehicleType": self.trip.vehicleType, "LegName": self.trip.legName])
}
}
}
} catch {
print(error)
}
}
}.resume()
print(self.Trips)
}
//Simple version just to replicate put your code within
private func FetchOriginCoordinates(completion: #escaping ([NominationStructure]) -> ()) {
let scheme = "https"
let host = "nominatim.openstreetmap.org"
let path = "/search"
let queryItemCountry = URLQueryItem(name: "country", value: "Sweden")
let queryItemCity = URLQueryItem(name: "city", value: "Stockholm")
let queryItemStreet = URLQueryItem(name: "street", value: self.origin.input)
let queryItemFormat = URLQueryItem(name: "format", value: "json")
var urlComponents = URLComponents()
urlComponents.scheme = scheme
urlComponents.host = host
urlComponents.path = path
urlComponents.queryItems = [queryItemCountry, queryItemCity, queryItemStreet, queryItemFormat]
URLSession.shared.dataTask(with: urlComponents.url!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode([NominationStructure].self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
private func FetchDestCoordinates(completion: #escaping ([NominationStructure]) -> ()) {
let scheme = "https"
let host = "nominatim.openstreetmap.org"
let path = "/search"
let queryItemCountry = URLQueryItem(name: "country", value: "Sweden")
let queryItemCity = URLQueryItem(name: "city", value: "Stockholm")
let queryItemStreet = URLQueryItem(name: "street", value: self.dest.input)
let queryItemFormat = URLQueryItem(name: "format", value: "json")
var urlComponents = URLComponents()
urlComponents.scheme = scheme
urlComponents.host = host
urlComponents.path = path
urlComponents.queryItems = [queryItemCountry, queryItemCity, queryItemStreet, queryItemFormat]
URLSession.shared.dataTask(with: urlComponents.url!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode([NominationStructure].self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
private func FetchOriginID(completion: #escaping (NearbyStopsStructure) -> ()) {
let nearbyStopsKey = "8444f9a2f75f4c27937a7165abd532a0"
let stopIDUrl = URL(string: "http://api.sl.se/api2/nearbystopsv2.json?key=\(nearbyStopsKey)&originCoordLat=\(self.origin.lat)&originCoordLong=\(self.origin.lon)&maxNo=1")
URLSession.shared.dataTask(with: stopIDUrl!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode(NearbyStopsStructure.self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
private func FetchDestID(completion: #escaping (NearbyStopsStructure) -> ()) {
let nearbyStopsKey = "8444f9a2f75f4c27937a7165abd532a0"
let stopIDUrl = URL(string: "http://api.sl.se/api2/nearbystopsv2.json?key=\(nearbyStopsKey)&originCoordLat=\(self.dest.lat)&originCoordLong=\(self.dest.lon)&maxNo=1")
URLSession.shared.dataTask(with: stopIDUrl!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode(NearbyStopsStructure.self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
}

How do I access integer value in JSON API?

I'm trying to retrieve an integer value from a JSON file in swift. I'm doing this as follows: self.trip.dist = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].dist) but I'm getting this error error.
Here is a link to the JSON file that I'm accessing. I'm trying to access the dist value.
These are my structures:
struct JSONStructure: Decodable {
var Trip: [TripStructure]
}
struct TripStructure: Decodable {
var LegList: LegListStructure
}
struct LegListStructure: Decodable {
var Leg: [LegStructure]
}
struct LegStructure: Decodable {
var Origin: StationStructure
var Destination: StationStructure
var Product: ProductStructure
var name: String
var type: String
var dist: Int
}
struct StationStructure: Decodable {
var time: String
var name: String
var date: String
}
struct ProductStructure: Decodable {
var catIn: String
}
// Just to condense my varibales
struct LocationInfo {
var iD = String()
var input = String()
var lat = String()
var lon = String()
var name = String()
var time = String()
var date = String()
var vehicleType = String()
var transportType = String()
var dist = String()
var legName = String()
}
Here is the function I'm using to call the function:
#Published var trip: LocationInfo = LocationInfo()
#Published var dest: LocationInfo = LocationInfo()
#Published var origin: LocationInfo = LocationInfo()
#Published var arrivalTime = String()
#Published var travelDate = String()
#Published var searchForArrival = String()
#Published var tripIndex = Int()
#Published var Trips: [Dictionary<String, String>] = []
public func FetchTrip() {
Trips.removeAll()
let tripKey = "40892db48b394d3a86b2439f9f3800fd"
let tripUrl = URL(string: "http://api.sl.se/api2/TravelplannerV3_1/trip.json?key=\(tripKey)&originExtId=\(self.origin.iD)&destExtId=\(self.dest.iD)&Date=\(self.travelDate)&Time=\(self.arrivalTime)&searchForArrival=\(self.searchForArrival)")
URLSession.shared.dataTask(with: tripUrl!) {data, response, error in
if let data = data {
do {
let decodedJson = try JSONDecoder().decode(JSONStructure.self, from: data)
self.tripIndex = decodedJson.Trip.count - 1
for i in 0..<decodedJson.Trip[self.tripIndex].LegList.Leg.count {
self.trip.transportType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].type
if self.trip.transportType == "WALK" {
self.origin.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.name
self.origin.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.time.prefix(5))
self.origin.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.date
self.dest.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.name
self.dest.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.time.prefix(5))
self.dest.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.date
self.trip.vehicleType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Product.catIn
self.trip.dist = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].dist) // This is where the problem lies
self.Trips.append(["Origin": self.origin.name, "Destination": self.dest.name, "OriginTime": self.origin.time, "DestTime": self.dest.time, "OriginDate": self.origin.date, "DestDate": self.dest.date, "TransportType": self.trip.transportType, "VehicleType": self.trip.vehicleType, "Distance": self.trip.dist])
}
else {
self.origin.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.name
self.origin.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.time.prefix(5))
self.origin.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.date
self.dest.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.name
self.dest.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.time.prefix(5))
self.dest.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.date
self.trip.vehicleType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Product.catIn
self.trip.legName = decodedJson.Trip[self.tripIndex].LegList.Leg[i].name
self.Trips.append(["Origin": self.origin.name, "Destination": self.dest.name, "OriginTime": self.origin.time, "DestTime": self.dest.time, "OriginDate": self.origin.date, "DestDate": self.dest.date, "TransportType": self.trip.transportType, "VehicleType": self.trip.vehicleType, "LegName": self.trip.legName])
}
}
} catch {
print(error)
}
}
}.resume()
}
I am getting this error in the console:
keyNotFound(CodingKeys(stringValue: "dist", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "Trip", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "LegList", intValue: nil), CodingKeys(stringValue: "Leg", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"dist\", intValue: nil) (\"dist\").", underlyingError: nil))
This site is great: https://app.quicktype.io You just paste in your json and it will write the Codable for you. QED.
You need to use an Optional Int to extract the dist values because not all legs have one.
Here's the minimum necessary to access the dist values:
func example() {
let url = URL(string: "https://api.sl.se/api2/TravelplannerV3_1/trip.json?key=40892db48b394d3a86b2439f9f3800fd&originExtId=300101416&destExtId=300101426&Date=2021-04-15&Time=08:00&searchForArrival=1")!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
let travelplanner = try? JSONDecoder().decode(Travelplanner.self, from: data)
print(travelplanner as Any)
}
}
.resume()
}
struct Travelplanner: Codable {
let trip: [Trip]
enum CodingKeys: String, CodingKey {
case trip = "Trip"
}
}
struct Trip: Codable {
let legList: LegList
enum CodingKeys: String, CodingKey {
case legList = "LegList"
}
}
struct LegList: Codable {
let leg: [Leg]
enum CodingKeys: String, CodingKey {
case leg = "Leg"
}
}
struct Leg: Codable {
let origin: Station
let destination: Station
let product: Product?
let name: String
let type: String
let dist: Int?
enum CodingKeys: String, CodingKey {
case origin = "Origin"
case destination = "Destination"
case product = "Product"
case name
case type
case dist
}
}
struct Station: Codable {
let name: String
let time: String
let date: String
}
struct Product: Codable {
let catIn: String
}
I'm glad you found a solution. There are other issues with your code that I feel the need to address.
The following code has the same effect but is, IMO, much cleaner:
func fetchTrip() {
let url = URL(string: "https://api.sl.se/api2/TravelplannerV3_1/trip.json?key=40892db48b394d3a86b2439f9f3800fd&originExtId=300101416&destExtId=300101426&Date=2021-04-15&Time=08:00&searchForArrival=1")!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
let decodedJson = try! JSONDecoder().decode(Travelplanner.self, from: data)
let lastLeg = decodedJson.trip.last?.legList.leg.last
self.tripIndex = decodedJson.trip.count - 1
self.trip.transportType = lastLeg?.type ?? ""
self.origin.name = lastLeg?.origin.name ?? ""
self.origin.time = lastLeg.map { String($0.origin.time.prefix(5)) } ?? ""
self.origin.date = lastLeg?.origin.date ?? ""
self.dest.name = lastLeg?.destination.name ?? ""
self.dest.time = lastLeg.map { String($0.destination.time.prefix(5)) } ?? ""
self.dest.date = lastLeg?.destination.date ?? ""
self.trip.vehicleType = lastLeg?.product?.catIn ?? ""
if let lastWalkLeg = decodedJson.trip.last?.legList.leg.last(where: { $0.type == "WALK" }) {
self.trip.dist = String(lastWalkLeg.dist ?? 0)
}
if let lastNonwalkLeg = decodedJson.trip.last?.legList.leg.last(where: { $0.type != "WALK" }) {
self.trip.legName = lastNonwalkLeg.name
}
self.trips = (decodedJson.trip.last?.legList.leg ?? [])
.map { $0.asDict }
}
}
.resume()
}
}
extension Leg {
var asDict: [String: String] {
[
"Origin": origin.name,
"Destination": destination.name,
"OriginTime": String(origin.time.prefix(5)),
"DestTime": String(destination.time.prefix(5)),
"OriginDate": origin.date,
"DestDate": destination.date,
"TransportType": type,
"VehicleType": product?.catIn ?? "",
].merging(type == "WALK" ? ["Distance": String(dist ?? 0)] : ["LegName": name], uniquingKeysWith: { $1 })
}
}
For most of the variables, you are assigning to the same var each time through the loop. This is just wasteful. Assign the values from the last element of the loop and be done with it.
Most of the work being done in the if...else... blocks are identical. Separate out the identical parts and only put the parts that care about the Leg.type in the if/else block. Do you actually use all the class properties in other methods of the class or were you just using them to make local variables in a weird way? (I'm assuming the former in the above code just in case.) If you don't need all the other properties in other methods, and the only point is to populate the trips array, you could unload most of the code (except the self.trips =... line of course.

JSON decoder The data couldn’t be read because it isn’t in the correct format

I am new to this. Somehow I am able to understand how to do this.
I am doing below, but it's giving error- The data couldn’t be read because it isn’t in the correct format.Can someone help me with this? I am stuck on this from past 4 days. I really appreciate.
import SwiftUI
import Foundation
import Combine
struct Movie: Decodable, Identifiable {
var id: Int
var video: String
var vote_count: String
var vote_average: String
var title: String
var release_date: String
var original_language: String
var original_title: String
}
struct MovieList: Decodable{
var results: [Movie]
___________
class NetworkingManager : ObservableObject{
var objectWillChange = PassthroughSubject<NetworkingManager, Never>()
#Published var movies = [Movie]()
init() {
load()
}
func load(){
let url = URL(string: "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=<HIDDEN>")!
URLSession.shared.dataTask(with: url){ (data, response, error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode([Movie].self, from: d)
DispatchQueue.main.async {
self.movies = decodedLists
}
}else {
print("No Data")
}
} catch {
print (error.localizedDescription)
}
}.resume()
}
}
This is how the response looks like:
{
"page": 1,
"results": [
{
"id": 419704,
"video": false,
"vote_count": 1141,
"vote_average": 6.2,
"title": "Ad Astra",
"release_date": "2019-09-17",
"original_language": "en",
"original_title": "Ad Astra",
"genre_ids": [
878
],
"backdrop_path": "/5BwqwxMEjeFtdknRV792Svo0K1v.jpg",
"adult": false,
"overview": "An astronaut travels to the outer edges of the solar system to find his father and unravel a mystery that threatens the survival of Earth. In doing so, he uncovers secrets which challenge the nature of human existence and our place in the cosmos.",
"poster_path": "/xJUILftRf6TJxloOgrilOTJfeOn.jpg",
"popularity": 227.167,
"media_type": "movie"
},
]
}
Code should fetch the data and hold in it the array I created. So that I can use it to display in the front end.
I had to consume the exact same API for a similar project and this is how I did it.
When calling:
let response = try JSONDecoder().decode(MovieResponse.self, from: data)
It needs to match the same properties that the JSON response returns.
Below you'll see a MovieResponse struct and the Movie class, which will list all of the properties and return types that the JSON response returns.
The type adopts Codable so that it's decodable using a JSONDecoder instance.
See this official example for more information regarding Codable.
A type that can convert itself into and out of an external representation.
Provided they match then the JSONDecoder() will work to decode the data.
ContentView.swift:
struct ContentView: View {
#EnvironmentObject var movieViewModel: MovieListViewModel
var body: some View {
MovieList(movie: self.movieViewModel.movie)
}
}
MovieListViewModel.swift:
public class MovieListViewModel: ObservableObject {
public let objectWillChange = PassthroughSubject<MovieListViewModel, Never>()
private var movieResults: [Movie] = []
var movie: MovieResults = [Movie]() {
didSet {
objectWillChange.send(self)
}
}
func load(url: String = "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=<HIDDEN>") {
guard let url = URL(string: url) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
guard let data = data else { return }
let response = try JSONDecoder().decode(MovieResponse.self, from: data)
DispatchQueue.main.async {
for movie in response.results {
self.movieResults.append(movie)
}
self.movie = self.movieResults
print("Finished loading Movies")
}
} catch {
print("Failed to decode: ", error)
}
}.resume()
}
}
MovieResponse.swift:
struct MovieResponse: Codable {
var page: Int
var total_results: Int
var total_pages: Int
var results: [Movie]
}
public class Movie: Codable, Identifiable {
public var popularity: Float
public var vote_count: Int
public var video: Bool
public var poster_path: String
public var id: Int
public var adult: Bool
public var backdrop_path: String
public var original_language: String
public var original_title: String
public var genre_ids: [Int]
public var title: String
public var vote_average: Float
public var overview: String
public var release_date: String
enum CodingKeys: String, CodingKey {
case popularity = "popularity"
case vote_count = "vote_count"
case video = "video"
case poster_path = "poster_path"
case id = "id"
case adult = "adult"
case backdrop_path = "backdrop_path"
case original_language = "original_language"
case original_title = "original_title"
case genre_ids = "genre_ids"
case title = "title"
case vote_average = "vote_average"
case overview = "overview"
case release_date = "release_date"
}
public init(popularity: Float, vote_count: Int, video: Bool, poster_path: String, id: Int, adult: Bool, backdrop_path: String, original_language: String, original_title: String, genre_ids: [Int], title: String, vote_average: Float, overview: String, release_date: String) {
self.popularity = popularity
self.vote_count = vote_count
self.video = video
self.poster_path = poster_path
self.id = id
self.adult = adult
self.backdrop_path = backdrop_path
self.original_language = original_language
self.original_title = original_title
self.genre_ids = genre_ids
self.title = title
self.vote_average = vote_average
self.overview = overview
self.release_date = release_date
}
public init() {
self.popularity = 0.0
self.vote_count = 0
self.video = false
self.poster_path = ""
self.id = 0
self.adult = false
self.backdrop_path = ""
self.original_language = ""
self.original_title = ""
self.genre_ids = []
self.title = ""
self.vote_average = 0.0
self.overview = ""
self.release_date = ""
}
}
public typealias MovieResults = [Movie]
MovieCellViewModel.swift:
public class MovieCellViewModel {
private var movie: Movie
public init(movie: Movie) {
self.movie = movie
}
public func getTitle() -> String {
return self.movie.title
}
// add more properties or functions here
}
MovieCell.swift:
struct MovieCell: View {
var movieCellViewModel: MovieCellViewModel
var body: some View {
Text(self.movieCellViewModel.getTitle())
}
}
MovieList.swift:
struct MovieList: View {
#EnvironmentObject var movieViewModel: MovieListViewModel
var movie: MovieResults
var body: some View {
List(self.movie) { movie in
MovieCell(movieCellViewModel: MovieCellViewModel(movie: movie))
}
}
}
I had a same error, but for the properties inside the struct for JSON object doesn't match JSON file. I fixed this error by setting the property names same as JSON file properties, and in the same order.
Swift file:
struct Coin: Decodable {
var asset_id_base: String
var rates: [CoinDetail]
}
struct CoinDetail: Decodable {
var time: String
var asset_id_quote: String
var rate: Double
}
JSON File:
{
"asset_id_base": "BTC",
"rates": [
{
"time": "2020-11-08T07:50:02.2865270Z",
"asset_id_quote": "CZK",
"rate": 328886.3419989546
},
{
"time": "2020-11-08T07:51:15.0750421Z",
"asset_id_quote": "GPL2",
"rate": 92123.4454168586
},
The Movie struct doesn't match the API response. You missed at least top level, like page and results array. Here you can paste your JSON answer and get the needed struct:
// MARK: - APIAnswer
struct APIAnswer: Codable {
let page: Int
let results: [Result]
}
// MARK: - Result
struct Result: Codable {
let id: Int
let video: Bool
let voteCount: Int
let voteAverage: Double
let title, releaseDate, originalLanguage, originalTitle: String
let genreIDS: [Int]
let backdropPath: String
let adult: Bool
let overview, posterPath: String
let popularity: Double
let mediaType: String
enum CodingKeys: String, CodingKey {
case id, video
case voteCount = "vote_count"
case voteAverage = "vote_average"
case title
case releaseDate = "release_date"
case originalLanguage = "original_language"
case originalTitle = "original_title"
case genreIDS = "genre_ids"
case backdropPath = "backdrop_path"
case adult, overview
case posterPath = "poster_path"
case popularity
case mediaType = "media_type"
}
}
Then you need to use the top level struct, like:
// ...
let decodeResult = try JSONDecoder().decode([APIAnswer].self, from: d)
let movieList = decodeResult.results
// the other advice: don't just take answer and put it to the array.
// API can have errors too, so you can get the array with 2 equal id, for example
update checked your code for second time: you use
let decodeResult = try JSONDecoder().decode([Movie].self, from: d)
instead of:
let decodeResult = try JSONDecoder().decode(MovieList.self, from: d)
P.S. better to attach also the full error from Xcode in future