skip to Main Content

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"
    }
}

}

2

Answers


  1. 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)
    
    Login or Signup to reply.
  2. 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)
        }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search