Display a list from Observable Object SwiftUI - json

I'm trying to decode some JSON and print it to a list, currently getting this error message,
ForEach, Int, Text> count (626) != its initial count (0). ForEach(_:content:) should only be used for constant data. Instead conform data to Identifiable or use ForEach(_:id:content:) and provide an explicit id!
I can print a specific ticket by accessing result[0]
but I'm not able to return all results to the view.
Here is my ListView
struct WOListView: View {
#EnvironmentObject var ticketData:ControlCenter
var body: some View {
VStack {
Text(String(self.ticketData.jsonData?.result[0].ticketID?.ticketID ?? 0))
List{
ForEach(0 ..< (self.ticketData.jsonData?.result.count ?? 0)) {
Text(String(self.ticketData.jsonData?.result[$0].ticketID?.ticketID ?? 0))
}
}
}
}
}
struct WOListView_Previews: PreviewProvider {
static var previews: some View {
WOListView().environmentObject(ControlCenter())
}
}
WorkOrderResults.swift
struct WorkOrderResults: Codable{
var result:[Result]
enum CodingKeys:String, CodingKey{
case result = "Result"
}
struct Result:Codable{
var ticketID:TicketID?
var summary:Summary?
var status:Status?
var catagory:Catagory?
enum CodingKeys:String, CodingKey{
case ticketID = "1"
case summary = "22"
case status = "96"
case catagory = "164"
}
struct TicketID:Codable {
var ticketID:Int?
enum CodingKeys: String, CodingKey{
case ticketID = "Value"
}
}

Found the answer to my question here!
view-is-not-rerendered-in-nested-foreach-loop!
Change WOListView to look like this...
var body: some View {
VStack {
Text(String(self.ticketData.jsonData?.result[0].ticketID?.ticketID ?? 0))
ForEach(0 ..< (self.ticketData.jsonData?.result.count ?? 0), id: \.self) {
Text(String(self.ticketData.jsonData?.result[$0].ticketID?.ticketID ?? 0))
}
}
}

Related

Converting JSON 64bit string to an Image - difficulty

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)

SwiftUI: Instance member 'enableFilter' cannot be used on type 'ContentView'; did you mean to use a value of this type instead?

I'm a beginner with SwiftUI and I wanted to create a dynamic list with the birthdays of people. I also wanted to integrate a filter that facilitates to find birthdays. But in the lines:
if enableFilter == true {
return json.filter {$0.BirthdayString.contains(filter(date: filterDate))}
} else {
return json
}
I always get these errors:
Instance member 'enableFilter' cannot be used on type 'ContentView';
did you mean to use a value of this type instead?
and
Instance member 'filterDate' cannot be used on type 'ContentView'; did
you mean to use a value of this type instead?
I think I understand why the errors are present but I don't no how to fix it. I tried:
#State static var
but then I cannot change the values with my
filterView
Thank you for your help, here is the full source code:
import SwiftUI
struct person: Codable, Hashable, Identifiable {
var id: Int
var Birthday: Date
var BirthdayString: String
}
func filter(date: Date) -> String {
let DateComponents = Calendar.current.dateComponents([.year, .month, .day], from: date)
let DateComponentsString: String = "\(DateComponents.day)/\(DateComponents.month)/\(DateComponents.year)"
return DateComponentsString
}
struct ContentView: View {
#State var people: [person] = {
guard let data = UserDefaults.standard.data(forKey: "people") else { return [] }
if let json = try? JSONDecoder().decode([person].self, from: data) {
if enableFilter == true {
return json.filter {$0.BirthdayString.contains(filter(date: filterDate))}
} else {
return json
}
}
return []
}()
#State var filterDate: Date = Date()
#State var enableFilter: Bool = false
#State var showFilter: Bool = false
#State var newPersonDate: Date = Date()
var body: some View {
NavigationView {
VStack {
HStack {
DatePicker(selection: $newPersonDate, label: {Text("Birthday")}).padding()
Button(action: {didTapAddTask()}, label: {Text("Add")}).padding()
}
List {
ForEach($people) { $person in
Text("\(person.Birthday)")
}
}
}
.navigationTitle(Text("People's birthday"))
}
}
var filterView: some View {
VStack {
DatePicker(selection: $filterDate, label: {Text("Date")}).padding()
Toggle(isOn: $enableFilter, label: {Text("enable filter")}).padding()
}
}
func didTapAddTask() {
let id = people.reduce(0) { max($0, $1.id) } + 1
people.insert(person(id: id, Birthday: newPersonDate, BirthdayString: filter(date: newPersonDate)), at: 0)
newPersonDate = Date()
save()
}
func save() {
guard let data = try? JSONEncoder().encode(people) else { return }
UserDefaults.standard.set(data, forKey: "people")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You can use another Computed property which will get the data based on some another property's value. i.e enableFilter.
#State var people: [person] = {
guard let data = UserDefaults.standard.data(forKey: "people") else { return [] }
if let json = try? JSONDecoder().decode([person].self, from: data) {
return json
}
return []
}()
var data : [person] {
if enableFilter {
return people.filter {$0.BirthdayString.contains(filter(date: filterDate))}
} else {
return people
}
}
And use this property to get the relevant data:
var body: some View {
NavigationView {
VStack {
HStack {
DatePicker(selection: $newPersonDate, label: {Text("Birthday")}).padding()
Button(action: {didTapAddTask()}, label: {Text("Add")}).padding()
}
List {
ForEach(data) { person in // <<--- Here `data`
Text("\(person.Birthday)")
}
}
}
.navigationTitle(Text("People's birthday"))
}
}

SwiftUI - Display nested Array in JSON response - try breaking up the expression into distinct sub-expressions

I have the following JSON response.
{
"results":[
{
"TrimName":"Truck",
"VIN":"GTJ3E18JF164623",
"Price": 47600,
"Odometer": 35511,
"OdometerType": "Miles",
"OptionCodeSpecs":{
"C_SPECS":{
"code":"C_SPECS",
"name":"Specs",
"options":[
{
"code":"SPECS_ACCELERATION",
"name":"5.2 sec",
"long_name":"5.2 seconds",
"description":"0-60 mph"
},
{
"code":"SPECS_TOP_SPEED",
"name":"140 mph",
"long_name":"140 miles per hour",
"description":"Top Speed"
},
{
"code":"SPECS_RANGE",
"name":"264 mi",
"long_name":"264 miles",
"description":"range (EPA)"
}
]
}
}
}
],
"total_matches_found":"53"
}
I built the Struct using app.quicktype.io.
// MARK: - InventoryModel
struct InventoryModel: Codable {
let results: [Result]
let totalMatchesFound: String
enum CodingKeys: String, CodingKey {
case results
case totalMatchesFound = "total_matches_found"
}
}
// MARK: - Result
struct Result: Codable {
let trimName, vin: String
let price, odometer: Int
let odometerType: String
let optionCodeSpecs: OptionCodeSpecs
enum CodingKeys: String, CodingKey {
case trimName = "TrimName"
case vin = "VIN"
case price = "Price"
case odometer = "Odometer"
case odometerType = "OdometerType"
case optionCodeSpecs = "OptionCodeSpecs"
}
}
// MARK: - OptionCodeSpecs
struct OptionCodeSpecs: Codable {
let cSpecs: CSpecs
enum CodingKeys: String, CodingKey {
case cSpecs = "C_SPECS"
}
}
// MARK: - CSpecs
struct CSpecs: Codable {
let code, name: String
let options: [Option]
}
// MARK: - Option
struct Option: Codable {
let code, name, longName, optionDescription: String
enum CodingKeys: String, CodingKey {
case code, name
case longName = "long_name"
case optionDescription = "description"
}
}
I am able to successfully retrieve the JSON response successfully.
#Published var getInventoryQuery: InventoryModel = InventoryModel(results: [], totalMatchesFound: "")
In my ContentView I am able to display the data at the Results object, but I am unable to get to the options array in the OptionCodeSpecs object without the following error:
The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
Xcode Version 12.5.1 (12E507)
#ObservedObject var inv = GetInventory()
The following works great. Code tripped out for clarity.
ScrollView(.horizontal, showsIndicators: false){
HStack(spacing: 10){
ForEach(inv.getInventoryQuery.results, id: \.trimName) { inventory in
VStack(alignment: .leading) {
VStack(alignment: .center) {
Text(inventory.trimName)
However, when I try and get to the options array, I get the following error:
The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
HStack(alignment: .center){
ForEach(inv.getInventoryQuery.results[inventory].optionCodeSpecs.cSpecs.options) { options in
Text(options.longName)
.font(.headline)
.fontWeight(.light)
Image(systemName: "battery.100")
.renderingMode(.original)
}
}

Download nested JSON from URL and make Observable Object SwiftUI, ERROR: "Missing Argument for parameter 'from' in call"

I am trying to download a nested JSON file from an URL. I want to make it Observable Object so that my App will update its view when the download is finished.
Here I have my structures and the class I have created in order to make it an Observable Object. This is also where my Error: "Missing argument in parameter 'from' in call" appears: (Thanks for any help!)
struct SpotDetail: Codable {
var spot: String
var day0: DayDetail
var day1: DayDetail
var day2: DayDetail
enum CodingKeys: String, CodingKey {
case spot = "spot"
case day0 = "day0"
case day1 = "day1"
case day2 = "day2"
}
}
struct DayDetail: Codable {
var date: String
var wind: [Int]
var gust: [Int]
enum CodingKeys: String, CodingKey {
case date = "date"
case wind = "wind"
case gust = "gust"
}
}
class networkSpotData: ObservableObject {
let objectWillChange = ObjectWillChangePublisher()
var WindSpot = SpotDetail() { // ERROR: "Missing Argument for parameter 'from' in call"
willSet {
self.objectWillChange.send()
}
}
}
And the function that downloads from the URL:
extension ContentView {
func loadspotdata(url: String) {
guard let url = URL(string: "https://raw.githubusercontent.com/Bene2907/Bene2907.github.io/main/template.json") else {
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let response_obj = try? JSONDecoder().decode(SpotDetail.self, from: data) {
DispatchQueue.main.async {
spotData.WindSpot = response_obj
}
}
}
}.resume()
}
}
Finally I have my view to present the data:
struct ContentView: View {
#ObservedObject var spotData = networkSpotData()
var body: some View {
VStack {
Text("\(spotData.WindSpot.spot)")
.padding()
Text("\(spotData.WindSpot.day0.date)")
.padding()
}.onAppear(perform: {
loadspotdata()
})
}
}
To use default constructor in
var WindSpot = SpotDetail() { // ERROR: "Missing Argument for parameter 'from' in call"
you have to initialize properties with default values in SpotDetail, like
struct SpotDetail: Codable {
var spot: String = ""
var day0: DayDetail = DayDetail() // this also should be initialised properties
...
}

How to customize raw value in an enum

What is my json looks like :
{
"2019-08-27 19:00:00": {
"temperature": {
"sol":292
}
}
,
"2019-08-28 19:00:00": {
"temperature": {
"sol":500
}
}
}
Here is a method to get the current next five days in the format needed :
func getFormatedDates() -> [String] {
let date = Date()
let format = DateFormatter()
format.dateFormat = "yyyy-MM-dd"
var dateComponents = DateComponents()
var dates = [String]()
for i in 0...4 {
dateComponents.setValue(i, for: .day)
guard let nextDay = Calendar.current.date(byAdding: dateComponents, to: date) else { return [""] }
let formattedDate = format.string(from: nextDay)
dates.append(formattedDate + " " + "19:00:00")
}
return dates
}
Because date key in the API constantly changes, I need dynamic keys. I would like to use this method inside an enum like in my Model :
var dates = getFormatedDates()
let firstForcast: FirstForcast
let secondForcast: SecondForcast
enum CodingKeys: String, CodingKey {
case firstForcast = dates[0]
case secondForcast = dates[1]
}
Any ideas ?
Create related types as below,
// MARK: - PostBodyValue
struct PostBodyValue: Codable {
let temperature: Temperature
}
// MARK: - Temperature
struct Temperature: Codable {
let sol: Int
}
typealias PostBody = [String: PostBodyValue]
and decode like this,
do {
let data = // Data from the API
let objects = try JSONDecoder().decode(PostBody.self, from: data)
for(key, value) in objects {
print(key)
print(value.temperature.sol)
}
} catch {
print(error)
}