Home > Enterprise >  Issue with trying to reference a field in a JSON element from a related selection in a SwiftUI Picke
Issue with trying to reference a field in a JSON element from a related selection in a SwiftUI Picke

Time:06-19

In response to @Vadian's request I have provided the code for a redacted View to add a Client with the relevant code for context as well as a sample JSON element. I have attempted various ways to create a reference back to the 'flag' in the JSON element related to the 'selectedCountry' over the last two days with no progress, as I understand that JSON does not have reference capability. The code inside the Picker works perfectly and displays a list of 'country.name's & 'country.flagImage's. I am trying to display the flag again after exiting the Picker. The secondary issue that 'selectedCountry' default setting of "Canada" does not appear initially in the Picker despite being an @State var. Any help would be appreciated!!!

CountryModel:

import SwiftUI

class Country: Codable, Identifiable, Comparable, ObservableObject {
    
        static func == (lhs: Country, rhs: Country) -> Bool {
            lhs.name == rhs.name
        }
        
        static func < (lhs: Country, rhs: Country) -> Bool {
            lhs.name < rhs.name
        }
    let name: String
    let isoAlpha3: String
    let flag: Data?
    var flagImage : UIImage? {
        guard let flagData = flag else { return nil }
        return UIImage(data: flagData)
    }
    
    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
    }
}

View (simplified):

import SwiftUI

struct SampleAddClient: View {
    @Environment(\.managedObjectContext) var dbContext
    @Environment(\.dismiss) var dismiss
    
    let countries: [Country] = Bundle.main.decode("Countries.json")
    @State private var inputName: String = ""
    @State private var inputAddress: String = ""
    @State private var selectedCountry: String = "Canada"
    let client: ClientEntity?
    
    var body: some View {
        VStack(spacing: 12) {
            HStack {
                Text("Name:")
                TextField("Client Name", text: $inputName)
                    .textFieldStyle(.roundedBorder)
            }//: HSTACK1
            HStack {
                Text("Address")
                TextField("Client Address", text: $inputAddress)
                    .textFieldStyle(.roundedBorder)
            }//: HSTACK2
            HStack {
                Text("Client's Host Country:")
                Spacer()
                
                VStack {
                    Picker(selection: $selectedCountry,
                           label: Text("Host Country:")) {
                        ForEach(countries) { country in
                            HStack {
                                Text(country.name).tag(country.name)
                                // Find Flag
                                if let flagImage = country.flagImage {
                                    Image(uiImage: flagImage).tag(country.flag)
                                } else {
                                    let _ = print("Fail")
                                }
                            }
                        }
                    }
                           .padding(.horizontal, 0)
                           .padding()
                           .pickerStyle(MenuPickerStyle())
                }//: INNER PICKER VSTACK
            }//: HSTACK3
            //Image(THIS IS WHERE THE FLAG IMAGE SHOULD GO.)
        }//: OUTER VSTACK
        .disableAutocorrection(true)
        .padding()
        .navigationBarTitle("Add Client")
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button("Save") {
                    let newName = inputName.trimmingCharacters(in: .whitespaces)
                    let newAddress = inputAddress.trimmingCharacters(in: .whitespaces)
                    let newCountry = selectedCountry
                    
                    if !newName.isEmpty {
                        Task(priority: .high) {
                            await storeClient(clientName: newName, clientAddress: newAddress, clientHostCountry: newCountry)
                        }
                    }
                }
            }
        }//: TOOLBAR
    }
    
    func storeClient(clientName: String, clientAddress: String, clientHostCountry: String) async {
        await dbContext.perform {
            
            let newClient = ClientEntity(context: dbContext)
            newClient.clientName = clientName
            newClient.clientAddress = clientAddress
            newClient.clientHostCountry = clientHostCountry
        }
        do {
            try dbContext.save()
            dismiss()
        } catch {
            print("Error saving record! \(error.localizedDescription)")
        }
    }
}

struct SampleAddClient_Previews: PreviewProvider {
    static var previews: some View {
        SampleAddClient(client: nil)
    }
}

JSON Sample:

{ "id": 39, "name": "Canada", "isoAlpha2": "CA", "isoAlpha3": "CAN", "isoNumeric": 124, "currency": { "code": "CAD", "name": "Dollar", "symbol": "$" }, "flag": "iVBORw0KGgoAAAANSUhEUgAAAB4AAAAUCAYAAACaq43EAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8 IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpFREI0MDA4MjE3NzMxMUUyODY3Q0FBOTFCQzlGNjlDRiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpFREI0MDA4MzE3NzMxMUUyODY3Q0FBOTFCQzlGNjlDRiI IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkVEQjQwMDgwMTc3MzExRTI4NjdDQUE5MUJDOUY2OUNGIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkVEQjQwMDgxMTc3MzExRTI4NjdDQUE5MUJDOUY2OUNGIi8 IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8 IgrEzAAAAaZJREFUeNrEVktqwlAUPVr/TRXqB0QMHXbSqXsQdCRuQbsBR3bmJBtw7A4cuwyLBQNCKdgOVBCpIv7aUx8PiVZtNCo9cHm5yb3v3E/eTWwEPrEOlwtot4FIBGfBbAbE40C3u3HbsZQb/APslj3LZUDTrDMvS80NcbnIToc7MZkY18JO2K8wHu/2mU7JSIS/eRxHRTmfA6USMBjI3gnkckAoBBQKgNd7oYwFEomt6Hl3t99 T8Z20zdyhWZTrvn8tt3jo1wbjd2 R2fc65HpNFksksEgqWlkv0/a7Zs 7TZZqZCKQj49kckk fr6Z8bmpY5GN5 nUqTTaeg2mwxuPRi327TUfxMLp3x u6dmks1KX8vErRbp8x1PLKReP4H4 5us1cjb28MJPR6yWiW/vk7sscD7O3l/b06qqqSuH3ScDj/Hi4XceB pqMpodIHJdXUFPDwAfj gqsYWTifw9gaEw8D19QUm12xGDgaG/vJCPj8b nAobc4yudYhMgsEDF3XpaygKNLmQDgsf9Y PszH4kWIYzFgsTiJeGjJM5M56Q/kR4ABAHxxYPgLzAdZAAAAAElFTkSuQmCC" },

CodePudding user response:

Put tag on entire picker's row

    HStack {
        Text(country.name)
        // Find Flag
        if let flagImage = country.flagImage {
            Image(uiImage: flagImage).tag(country.flag)
        } else {
            let _ = print("Fail")
        }
    }.tag(country.name)     // << here !!

CodePudding user response:

you could try a small re-structure of your code using an ObservableObject model to hold your countries: [Country]. And a selectedCountry: Country? for the Picker selection, such as in this example code (works well for me):

// -- here
struct Country: Codable, Identifiable, Comparable, Hashable {
    let id = UUID() // <-- here
    
    let name: String
    let isoAlpha3: String
    let flag: Data?
    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
}

// -- here
class CountryModel: ObservableObject {
    @Published var countries: [Country] = []
    
    init() {
        self.countries = Bundle.main.decode("Countries.json")
    }
}

struct SampleAddClient: View {
    @Environment(\.managedObjectContext) var dbContext
    @Environment(\.dismiss) var dismiss
    
    @StateObject var viewModel = CountryModel()  // <-- here

    @State private var inputName: String = ""
    @State private var inputAddress: String = ""
    @State private var selectedCountry: Country?   // <-- here
    let client: ClientEntity?
    
    var body: some View {
        VStack(spacing: 12) {
            HStack {
                Text("Name:")
                TextField("Client Name", text: $inputName)
                    .textFieldStyle(.roundedBorder)
            }//: HSTACK1
            HStack {
                Text("Address")
                TextField("Client Address", text: $inputAddress)
                    .textFieldStyle(.roundedBorder)
            }//: HSTACK2
            HStack {
                Text("Client's Host Country:")
                Spacer()
                
                VStack {
                    Picker(selection: $selectedCountry, label: Text("Host Country:")) {
                        ForEach(viewModel.countries) { country in
                            HStack {
                                Text(country.name)
                                if let flagImage = country.flagImage {
                                    Image(uiImage: flagImage)
                                } else {
                                    Image(systemName: "questionmark")
                                }
                            }.tag(country as Country?) // <-- here
                        }
                    }
                        .padding(.horizontal, 20)
                        .padding()
                        .pickerStyle(MenuPickerStyle())
                }
            }//: HSTACK3
            // THIS IS WHERE THE FLAG IMAGE SHOULD GO.
            // -- here
            VStack {
                Text("selectedCountry: \(selectedCountry?.name ?? "no name")")
                if let flagImage = selectedCountry?.flagImage {
                    Image(uiImage: flagImage)
                }
            }
        }//: OUTER VSTACK
        .onAppear {
            selectedCountry = viewModel.countries.first // <-- here if required
        }
        .disableAutocorrection(true)
        .padding()
        .navigationBarTitle("Add Client")
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button("Save") {
                    let newName = inputName.trimmingCharacters(in: .whitespaces)
                    let newAddress = inputAddress.trimmingCharacters(in: .whitespaces)
                    let newCountry = selectedCountry
                    
                    if !newName.isEmpty {
                        Task(priority: .high) {
                            await storeClient(clientName: newName, clientAddress: newAddress, clientHostCountry: newCountry)
                        }
                    }
                }
            }
        }//: TOOLBAR
    }
    
    func storeClient(clientName: String, clientAddress: String, clientHostCountry: String) async {
        await dbContext.perform {

            let newClient = ClientEntity(context: dbContext)
            newClient.clientName = clientName
            newClient.clientAddress = clientAddress
            newClient.clientHostCountry = clientHostCountry
        }
        do {
            try dbContext.save()
            dismiss()
        } catch {
            print("Error saving record! \(error.localizedDescription)")
        }
    }
}
  • Related