NSDictionary firstIndex identification Swift 5 - json

I'm working on an app where I need to store a Country and City in a Firebase database.
Besides storing, I also need to retrieve that info and present to the user in a pickerView. Given that, I need to read the Country and City from the Database, check what is their index and set it in pickerView.
Countries and Cities are store in JSON
{
"Country": [
{
"name": "UK",
"cities": [
{
"name": "London"
},
{
"name": "Manchester"
},
{
"name": "Bristol"
}
]
},
{
"name": "USA",
"cities": [
{
"name": "New York"
},
{
"name": "Chicago"
}
]
},
{
"name": "China",
"cities": [
{
"name": "Beijing"
},
{
"name": "Shanghai"
},
{
"name": "Shenzhen"
},
{
"name": "Hong Kong"
}
]
}
]
}
My code to read JSON is
// Declared in Class
var countryList = [NSDictionary]()
var selectedRow = [NSDictionary]()
var selectedCity = ""
var selectedCountry = ""
func readJson() {
if let path = Bundle.main.path(forResource: "Countries", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
if let jsonResult = jsonResult as? Dictionary<String, AnyObject>, let country = jsonResult["Country"] as? [NSDictionary] {
//handles the array of countries on your json file.
self.countryList = country
self.selectedRow = self.countryList.first?.object(forKey: "cities") as! [NSDictionary]
}
} catch {
print("error loading countries")
// handle error
}
}
}
The code above allows me to feed a UIPickerView with 2 sessions and the Country and the list of cities within that Country. From there, I can also identify which Country and City were selected.
AS part of my code I have a func that would allow me to identify what is indexes of the saved Country(countryIndex) and City(cityIndex) in UIPickerView so that I can set it and that's where my issues start
func indexOf(city: String, inCountry country: String, in countries: [Country]) -> (countryIndex: Int, cityIndex: Int)? {
// countries is an array of [Country]
// var countries = [Country]()
guard let countryIndex = countries.firstIndex(where: {$0.name == country}), let cityIndex = countries[countryIndex].cities.firstIndex(where: {$0.name == city}) else {return nil}
//let cityIndex = 0
return (countryIndex, cityIndex)
} // courtesy of #flanker
This func was working perfectly fine when my Countries and Cities were stored to a [Country] but is not working with NSDictionary coming from JSON.
I have tried to
1) Change [Country] by [NSDictionary], "countries" by "countryList" and "name" by "Country"
Here I receive and error "NSDictionary has no member Country"
I also tried to leave just $0 == Country which hasn't worked as well.
2) Tried also "countryList.firstIndex(of: "USA")" but got the error below
Cannot convert value of type 'String' to expected argument type 'NSDictionary'
Anyone would be able to assist? How can I make the func indexOf work again?
Thanks
Updated according to #vadian's suggestion
My updated code is
import UIKit
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var pickerView: UIPickerView!
#IBOutlet weak var countryLbl: UILabel!
var familyNames: [String] = []
var fontName = "Arial"
let fontCount = 0
var countryList = [Country]()
var selectedRow = [City]()
var selectedCity : City?
var selectedCountry : Country?
struct Root : Decodable {
let country : [Country] // better plural let countries
private enum CodingKeys : String, CodingKey { case country = "Country" }
}
struct Country : Decodable {
var name : String
var cities : [City]
}
struct City : Decodable {
var name : String
}
override func viewDidLoad() {
super.viewDidLoad()
pickerView.delegate = self
pickerView.dataSource = self
fontName = "HelveticaNeue"
}
func indexOf(city: String, inCountry country: String, in countries: [Country]) -> (countryIndex: Int, cityIndex: Int)? {
guard let countryIndex = countries.firstIndex(where: {$0.name == country}), let cityIndex = countries[countryIndex].cities.firstIndex(where: {$0.name == city}) else {return nil}
return (countryIndex, cityIndex)
}
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
if component == 0 {
return 80
} else {
return 300
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 2
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
if component == 0 {
return countryList.count
} else {
return selectedRow.count
}
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
var rowTitle = ""
let pickerLabel = UILabel()
pickerLabel.textColor = UIColor.blue
switch component {
case 0:
rowTitle = countryList[row].name
case 1:
rowTitle = selectedRow[row].name
default:
break
}
pickerLabel.text = rowTitle
pickerLabel.font = UIFont(name: fontName, size: 20.0)
pickerLabel.textAlignment = .center
return pickerLabel
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
pickerView.reloadAllComponents()
if component == 0 {
self.selectedCountry = self.countryList[row]
self.selectedRow = self.countryList[row].cities
pickerView.reloadComponent(1)
self.pickerView.selectRow(0, inComponent: 1, animated: true)
self.selectedCity = self.selectedRow[0]
} else {
self.selectedCity = self.selectedRow[row]
}
if let indexes = indexOf(city: self.selectedCity!.name, inCountry: self.selectedCountry!.name, in: countryList) {
//do something with indexes.countryIndex and indexes.cityIndex
print("This is the result \(indexes.cityIndex) and \(indexes.countryIndex)")
}
countryLbl.text = "The right answer is: \(self.selectedCountry?.name) and the city is \(self.selectedCity?.name)"
}
func readJson() {
let url = Bundle.main.url(forResource: "Countries", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
let jsonResult = try JSONDecoder().decode(Root.self, from: data)
//handles the array of countries on your json file.
self.countryList = jsonResult.country
self.selectedRow = self.countryList.first!.cities
} catch {
print("error loading countries", error)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
readJson()
}
}

You have to decode the JSON in the bundle also into custom structs
struct Root : Decodable {
let country : [Country] // better plural let countries
private enum CodingKeys : String, CodingKey { case country = "Country" }
}
struct Country : Decodable {
let name : String
let cities : [City]
}
struct City : Decodable {
let name : String
}
var countryList = [Country]()
var selectedRow : [City]()
var selectedCity : City?
var selectedCountry : Country?
func readJson() {
let url = Bundle.main.url(forResource: "Countries", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
let jsonResult = try JSONDecoder().decode(Root.self, from: data)
//handles the array of countries on your json file.
self.countryList = jsonResult.country
self.selectedRow = self.countryList.first!.cities
} catch {
print("error loading countries", error)
}
}
Then your method indexOf(city:inCountry:in:) works
As the file is in the application bundle consider to omit the root dictionary "Country" and decode [Country].self.
The usual side notes:
Do not use NS... collection types in Swift. You throw away the type information. Use native types.
.mutableContainers and .mutableLeaves are pointless in Swift. Apart from that ironically you assign the value to an immutable constant anyway.
A JSON dictionary in Swift 3+ is always value type [String:Any] not reference type[String:AnyObject].

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)

JSON Decoder not working when inserting data into CoreData in Swift

My app consists of a ProductFamily entity with a name attribute and an array of PartDetail dictionaries defined as a one-to-many relationship in CoreData. For each ProductFamily, I can have many PartDetail entities (PartNumbers) but for each PartDetail, it can only be associated with one ProductFamily. My example has 5 ProductFamilies, each with an array of 5 PartDetail dictionaries. I'm struggling to get my JSON decoder correct. It's not importing any data into CoreData. You can clone my sample project here:
https://github.com/jegrasso19/ProductFinder-Test2.git
A sample of my JSON data looks like this:
[
{
"Product Family 1": [
{
"partNumber": "160-9013-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9104-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9105-900",
"orderable": false,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9108-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9109-900",
"orderable": true,
"pnDescription": "Part Number Description"
}
]
},
{
"Product Family 2": [
{
"partNumber": "160-9113-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9114-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9115-900",
"orderable": false,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9116-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9201-900",
"orderable": true,
"pnDescription": "Part Number Description"
}
]
}
]
My ProductFamilyJSON Decoder file and ProductFamilyProperties looks like this:
import Foundation
struct ProductFamilyJSON: Decodable {
// Struct that conforms with CodingKey so we can retrieve the product family name as a key
//
private struct JSONCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
// This is the dictionary that contains the JSON data
// The key is the ProductFamily name, and the value is an array of PartDetailInfo.
//
private(set) var productFamilies = [ProductFamilyProperties]()
init(from decoder: Decoder) throws {
var rootContainer = try decoder.unkeyedContainer()
let nestedProductFamilyContainer = try rootContainer.nestedContainer(keyedBy: JSONCodingKeys.self)
// This is where my code fails. When decoding the JSON file,
// it never goes into the while loop.
var productFamily = try ProductFamilyProperties(from: decoder)
while !rootContainer.isAtEnd {
let productFamilyKey = nestedProductFamilyContainer.allKeys.first!
if var partNumberArrayContainer = try? nestedProductFamilyContainer.nestedUnkeyedContainer(forKey: productFamilyKey) {
var partNumbers = Array<PartDetailInfo>()
while !partNumberArrayContainer.isAtEnd {
if let partNumber = try? partNumberArrayContainer.decode(PartDetailInfo.self) {
partNumbers.append(partNumber)
}
}
productFamily.code = UUID().uuidString
productFamily.name = productFamilyKey.stringValue
productFamily.partNumbers = partNumbers
productFamilies.append(productFamily)
}
}
print(productFamilies)
}
}
import Foundation
struct ProductFamilyProperties : Decodable {
var code: String
var name: String
var partNumbers: Array<PartDetailInfo>
enum CodingKeys: String, CodingKey {
case code
case name
case partNumbers
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let rawCode = try? values.decode(String.self, forKey: .code)
let rawName = try? values.decode(String.self, forKey: .name)
let rawPartNumbers = try? values.decode(Array<PartDetailInfo>.self, forKey: .partNumbers)
guard let code = rawCode,
let name = rawName,
let partNumbers = rawPartNumbers
else {
throw myError.programError("Missing Data from Product Family")
}
self.code = code
self.name = name
self.partNumbers = partNumbers
}
var dictionaryValue: [String: Any] {
[
"code": code,
"name": name,
"partNumbers": partNumbers
]
}
}
In my ProductFamilyJSON file, it seems to quit at defining the productFamily variable, which is based on my ProductFamilyProperties. This is apparently wrong but I don't know what it should be defined as. This is my first iOS app I'm trying to develop and learn from. I've spent a while learning CoreData and I've seen so many examples but very few use NSBatchInsertRequest and everyone seems to do this a little differently. I would appreciate some insight on getting this to work. Thanks.
Here is my CoreDataManager class, which contains the NSBatchInsertRequest for reference.
import Foundation
import CoreData
class CoreDataManager: ObservableObject {
let persistentContainer: NSPersistentContainer
static var shared = CoreDataManager()
var viewContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
private init() {
persistentContainer = NSPersistentContainer(name: "ProductFinderTest")
persistentContainer.loadPersistentStores { (description, error) in
if let error = error {
fatalError("Unable to initialize Core Data \(error)")
}
}
let directories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
print(directories[0])
}
func newTaskContext() -> NSManagedObjectContext {
let taskContext = persistentContainer.newBackgroundContext()
taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
taskContext.undoManager = nil
return taskContext
}
}
extension CoreDataManager {
func fetchProductData() async throws {
guard let url = Bundle.main.url(forResource: "ProductFamilies", withExtension: "json"),
let jsonData = try? Data(contentsOf: url)
else {
throw myError.programError("Failed to receive valid response and/or Product Family data.")
}
do {
let jsonDecoder = JSONDecoder()
// ProductFamilyJSON uses this code
let productFamilyJSON = try jsonDecoder.decode(ProductFamilyJSON.self, from: jsonData)
let productFamilyList = productFamilyJSON.productFamilies
print("Received \(productFamilyList.count) Product records.")
print("Start importing product data to the store...")
try await importProductData(from: productFamilyList)
print("Finished importing product data.")
} catch {
throw myError.programError("Wrong Data Format for Product Families")
}
}
private func importProductData(from productList: [ProductFamilyProperties]) async throws {
guard !productList.isEmpty else { return }
let taskContext = newTaskContext()
taskContext.name = "importProductDataContext"
taskContext.transactionAuthor = "importProductData"
try await taskContext.perform {
let batchInsertRequest = self.productListBatchInsertRequest(with: productList)
if let fetchResult = try? taskContext.execute(batchInsertRequest),
let batchInsertResult = fetchResult as? NSBatchInsertResult,
let success = batchInsertResult.result as? Bool, success {
return
}
else {
throw myError.programError("Failed to execute ProductList batch import request.")
}
}
print("Successfully imported Product data.")
}
private func productListBatchInsertRequest(with productList: [ProductFamilyProperties]) -> NSBatchInsertRequest {
var index = 0
let total = productList.count
let batchInsertRequest = NSBatchInsertRequest(entity: ProductFamily.entity(), dictionaryHandler: { dictionary in
guard index < total else { return true }
dictionary.addEntries(from: productList[index].dictionaryValue)
index += 1
return false
})
return batchInsertRequest
}
func requestProductFamilies() -> NSFetchedResultsController<ProductFamily> {
var fetchedResultsController: NSFetchedResultsController<ProductFamily>!
let request: NSFetchRequest = ProductFamily.fetchProductFamilyRequest()
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
fetchedResultsController = NSFetchedResultsController(fetchRequest: request,
managedObjectContext: viewContext,
sectionNameKeyPath: nil,
cacheName: nil)
try? fetchedResultsController.performFetch()
return fetchedResultsController
}
func deleteProductData() async throws {
let taskContext = self.newTaskContext()
let fetchedResultsController = requestProductFamilies()
try fetchedResultsController.performFetch()
let productFamilies = (fetchedResultsController.fetchedObjects ?? []).map(ProductFamilyViewModel.init)
guard !productFamilies.isEmpty else {
print("ProductFamily database is empty.")
return
}
let objectIDs = productFamilies.map { $0.objectId }
print("Start deleting Product data from the store...")
try await taskContext.perform {
let batchDeleteRequest = NSBatchDeleteRequest(objectIDs: objectIDs)
guard let fetchResult = try? taskContext.execute(batchDeleteRequest),
let batchDeleteResult = fetchResult as? NSBatchDeleteResult,
let success = batchDeleteResult.result as? Bool, success
else {
throw myError.programError("Failed to execute Product Family batch delete request.")
}
}
print("Successfully deleted Product data.")
}
}
The problem was with how I was initializing the productFamily variable. I needed to initialize it with the actual values instead of as an empty variable. I also needed to move the nestedProductFamilyContainer inside the while loop. Here is the correct ProductFamilyJSON decoder file. In addition, I changed the partNumber attribute in my ProductFamily entity from NSSet to Array, which allowed more flexibility.
#vadian - I did remove the init(from:) and CodingKeys from ProductFamilyProperties as you suggested and it works just fine. Thanks for the input.
import Foundation
struct ProductFamilyJSON: Decodable {
// Struct that conforms with CodingKey so we can retrieve the product family name as a key
//
private struct JSONCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
// This is the dictionary that contains the JSON data
// The key is the ProductFamily name, and the value is an array of PartDetailInfo.
//
private(set) var productFamilies = [ProductFamilyProperties]()
init(from decoder: Decoder) throws {
var rootContainer = try decoder.unkeyedContainer()
while !rootContainer.isAtEnd {
let nestedProductFamilyContainer = try rootContainer.nestedContainer(keyedBy: JSONCodingKeys.self)
let productFamilyKey = nestedProductFamilyContainer.allKeys.first!
if var partNumberArrayContainer = try? nestedProductFamilyContainer.nestedUnkeyedContainer(forKey: productFamilyKey) {
var partNumbers = Array<PartDetailProperties>()
while !partNumberArrayContainer.isAtEnd {
if let partNumber = try? partNumberArrayContainer.decode(PartDetailProperties.self) {
partNumbers.append(partNumber)
}
}
let partNumbersSorted = partNumbers.sorted(by: { $0.partNumber < $1.partNumber })
let productFamily = ProductFamilyProperties(code: UUID().uuidString, name: productFamilyKey.stringValue, partNumbers: partNumbersSorted)
productFamilies.append(productFamily)
}
}
print(productFamilies)
}
}

How to build a model from a JSON file

This is my first time working with JSON in Swift and when i'm trying to parse the file with my model, this error appears:
The given data was not valid JSON.
I think the problem lies with how I do my model.
The Way I parse the JSON:
import SwiftUI
struct EmergencyView: View {
let emergency: [EmergencyNumberModel]
init() {
let url = Bundle.main.url(forResource: "emergency",
withExtension: "json")!
let data = try! Data(contentsOf: url)
emergency = try! JSONDecoder().decode([EmergencyNumberModel].self, from: data) //Error
}
var body: some View {
List(emergency, id: \.id) { emer in
if emer.Country != nil {
Label(emer.Country, systemImage: "quote.bubble")
.font(.headline)
} else{
Text(emer.Country)
}
}
navigationTitle("Emergency")
}
}
This is a fraction of the JSON i'm using, "emergency.json":
[
{
"Country": {
"Name": "Afghanistan",
"ISOCode": "AF",
"ISONumeric": "4"
},
"Ambulance": {
"All": [
"112"
]
},
"Fire": {
"All": [
"119"
]
},
"Police": {
"All": [
"119"
]
},
"Dispatch": {
"All": [
null
]
},
"Member_112": false,
"LocalOnly": true,
"Notes": false
},
.
.
.
]
This is my Model, "EmergencyNumberModel.swift":
struct EmergencyNumberModel: Codable, Identifiable {
var id = UUID()
let Country: String
let Ambulance: String
let Fire: String
let Police: String
let Dispatch: String
}
Do I need other variables in my model to access the inner keys or the data types of the variables are wrong?
Using https://app.quicktype.io/, to generate the swift strutures,
this is one basic approach to use your json data:
struct EmergencyView: View {
#State var emergencies: [EmergencyModel] = [] // <--- here
var body: some View {
List(emergencies) { emer in
if emer.country.name.isEmpty {
Text("no country name")
} else {
Label(emer.country.name, systemImage: "quote.bubble").font(.headline)
}
}
.onAppear {
if let emrgncy = loadData(from: "emergency") { // <--- here
emergencies = emrgncy
}
}
}
func loadData(from file: String) -> [EmergencyModel]? {
do {
if let filePath = Bundle.main.path(forResource: file, ofType: "json") {
let data = try Data(contentsOf: URL(fileURLWithPath: filePath))
let results = try JSONDecoder().decode([EmergencyModel].self, from: data)
return results
}
} catch {
print("----> error: \(error)") // <-- todo, deal with errors
}
return nil
}
}
struct EmergencyModel: Identifiable, Codable {
let id = UUID() // <--- here
let country: Country
let ambulance, fire, police: Ambulance
let dispatch: Dispatch
let member112, localOnly, notes: Bool
enum CodingKeys: String, CodingKey {
case country = "Country"
case ambulance = "Ambulance"
case fire = "Fire"
case police = "Police"
case dispatch = "Dispatch"
case member112 = "Member_112"
case localOnly = "LocalOnly"
case notes = "Notes"
}
}
struct Ambulance: Codable {
let all: [String]
enum CodingKeys: String, CodingKey {
case all = "All"
}
}
struct Country: Codable {
let name, isoCode, isoNumeric: String
enum CodingKeys: String, CodingKey {
case name = "Name"
case isoCode = "ISOCode"
case isoNumeric = "ISONumeric"
}
}
struct Dispatch: Codable {
let all: [JSONNull?]
enum CodingKeys: String, CodingKey {
case all = "All"
}
}
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
func hash(into hasher: inout Hasher) {
hasher.combine(0)
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
Generic version:
/// Reads a JSON file into a Model object of type T
class JsonReader<T> where T: Decodable {
static func loadData(from file: String) -> T? {
do {
if let filePath = Bundle.main.path(forResource: file, ofType: "json") {
let data = try Data(contentsOf: URL(fileURLWithPath: filePath))
let results = try JSONDecoder().decode(T.self, from: data)
return results
}
} catch {
print("Error: \(error)")
}
return nil
}
}
How to use:
let model = JsonReader<Model>.loadData(from: "FileName")!

I can't open the image. Parsing doesn't work

I can't open "url" in "images".
Parsing doesn't work. Please help. Thank you.
"images": [
{
"url": "https://ktar.com/wp-content/uploads/2020/03/ap_27df5153e3bf48198ebcfd40900446d6.jpg",
"width": 1280,
"height": 853,
"title": "Arizona reports 124 new coronavirus cases, five additional deaths",
"attribution": null
=======================================================
=======================================================
API
{
"path": "_news/2020-04-01-arizona-reports-124-new-coronavirus-cases-five-additional-deaths.md",
"title": "Arizona reports 124 new coronavirus cases, five additional deaths",
"excerpt": "The Arizona health department reported 124 new cases of coronavirus and five additional deaths on Wednesday morning, a day after the state's \"stay at home\" order went into effect.",
"heat": 145,
"tags": [
"US"
],
"type": "article",
"webUrl": "https://ktar.com/story/3056413/arizona-reports-124-new-coronavirus-cases-five-additional-deaths/",
"ampWebUrl": "https://ktar.com/story/3056413/arizona-reports-124-new-coronavirus-cases-five-additional-deaths/amp/",
"cdnAmpWebUrl": "https://ktar-com.cdn.ampproject.org/c/s/ktar.com/story/3056413/arizona-reports-124-new-coronavirus-cases-five-additional-deaths/amp/",
"publishedDateTime": "2020-04-01T09:09:00-07:00",
"updatedDateTime": null,
"provider": {
"name": "KTAR News",
"domain": "ktar.com",
"images": null,
"publishers": null,
"authors": null
},
"images": [
{
"url": "https://ktar.com/wp-content/uploads/2020/03/ap_27df5153e3bf48198ebcfd40900446d6.jpg",
"width": 1280,
"height": 853,
"title": "Arizona reports 124 new coronavirus cases, five additional deaths",
"attribution": null
}
],
"locale": "en-us",
"categories": [
"news"
],
"topics": [
"Coronavirus in US",
"Coronavirus",
"New Cases"
]
}
======================================================
======================================================
// SPORTNEWSVC.swift
// Hero
import Alamofire
import Kingfisher
import UIKit
class NewsVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var news = [Article]()
override func viewDidLoad() {
super.viewDidLoad()
getArticles()
}
func getArticles() {
let parameters: Parameters = ["Subscription-Key": "3009d4ccc29e4808af1ccc25c69b4d5d"]
Alamofire.request("https://api.smartable.ai/coronavirus/news/US", parameters: parameters).responseData { (response) in
guard let data = response.data else { return }
do {
// let json = try JSONSerialization.jsonObject(with: data, options: [])
// print(json)
let topHeadlinesResponse = try JSONDecoder().decode(TopHeadlinesResponse.self, from: data)
self.news = topHeadlinesResponse.news
self.collectionView?.reloadData()
} catch {
print(error)
}
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return news.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ArticleCell", for: indexPath) as? ArticleCell else { return UICollectionViewCell ()}
let article = news[indexPath.item]
cell.populate(with: article)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height: CGFloat = 277
let width = (collectionView.frame.width / 2) - 2
let size = CGSize(width: width, height: height)
return size
}
}
======================================================
======================================================
// Article.swift
// Hero
//
import Foundation
struct Article: Decodable {
let headline: String
let url: String?
private enum CodingKeys: String, CodingKey {
case headline = "title"
case url
}
}
======================================================
======================================================
//
// ArticleCell.swift
// Hero
import UIKit
import Kingfisher
class ArticleCell: UICollectionViewCell {
#IBOutlet weak var articleImageView: UIImageView!
#IBOutlet weak var headlieLabel: UILabel!
func populate(with article: Article){
headlieLabel.text = article.headline
if let url = article.url {
let url = URL(string: url)
articleImageView.kf.setImage(with: url)
}
}
}
======================================================
======================================================
import Foundation
struct TopHeadlinesResponse: Decodable {
let news: [Article]
}
The idea is that "images" in the JSON response you get is an array and you need to reflect this fact in Codable structure. The Decoding magic needs you to follow JSON data structure and namings: so if you meet field 'width' inside 'image' dictionary - you need to have a field named 'width' inside an 'image' field (e.g. having image as a separate struct)
you can read more about Decodables here
struct NewsApiResponse : Decodable {
let status : String
let updatedDateTime : Date?
let news : [Article]
}
struct Article : Decodable {
let id : Int
let title : String
let excerpt : String
let webUrl : String
let publishedDateTime : Date?
let images : [Image]
}
struct Image : Decodable {
let url : String
let width : Int
let height : Int
let title : String
}
*struct TopHeadLinesResponse: Codable {
let path, title, excerpt: String
let heat: Int
let tags: [String]
let type: String
let webURL, ampWebURL, cdnAmpWebURL: String
let publishedDateTime: Date
let updatedDateTime: JSONNull?
let provider: Provider
let images: [Image]
let locale: String
let categories, topics: [String]
enum CodingKeys: String, CodingKey {
case path, title, excerpt, heat, tags, type
case webURL
case ampWebURL
case cdnAmpWebURL
case publishedDateTime, updatedDateTime, provider, images, locale, categories, topics
}
}
// MARK: - Image
struct Image: Codable {
let url: String
let width, height: Int
let title: String
let attribution: JSONNull?
}
// MARK: - Provider
struct Provider: Codable {
let name, domain: String
let images, publishers, authors: JSONNull?
}
// MARK: - Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
extension UIImageView {
func loadImagee(_ urlString: String?, onSuccess: ((UIImage) -> Void)? = nil) {
self.image = UIImage()
guard let string = urlString else { return }
guard let url = URL(string: string) else { return }
self.sd_setImage(with: url) { (image, error, type, url) in
if onSuccess != nil, error == nil {
onSuccess!(image!)
}
}
}
}
// try using SD Web library code is above install SDweb instead of // king fisher
// check your struct also check this answer
https://stackoverflow.com/questions/60310054/im-having-troubles-displaying-an-image-from-json-in-a-table-view*

Parsing JSON data from alamofire into Array with Dictionary

I'm trying to parse JSON data from alamorefire as follows.
import UIKit
import Alamofire
import SwiftyJSON
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(.GET, "https://api.mynexttrainschedule.net/")
.responseJSON { response in
guard let object = response.result.value else {
print("Oh, no!!!")
return
}
let json = JSON(object);print(json)
let schedule = json[0]["schedule"]
}
}
}
If I print json, I have a data structure like the following (stated concisely).
[
{
"schedule" : [
{"departureTime" : "05:09", "destination" : "Boston", "trainType" : "Express"},
{"departureTime" : "05:19", "destination" : "Portland", "trainType" : "Rapid"},
{"departureTime" : "05:29", "destination" : "Boston", "trainType" : "Express""}
],
"station" : "Grand Central",
"direction" : "North"
},
{
"schedule" : [
{"departureTime" : "05:11","destination" : "Washington, "trainType" : "Express""},
{"departureTime" : "05:23","destination" : "Baltimore, "trainType" : "Express""},
{"departureTime" : "05:35","destination" : "Richmond, "trainType" : "Local""}
],
"station" : "Grand Central",
"direction" : "South"
}
]
Now, how can I save the schedule array with a dictionary (departureTime, destination...) through or not through SwiftyJSON?
Thanks.
UPDATE
The following is my own solution.
import Alamofire
import SwiftyJSON
class ViewController: UIViewController {
var scheduleArray = [Dictionary<String,String>]()
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(.GET, "https://api.mynexttrainschedule.net/")
.responseJSON { response in
guard let object = response.result.value else {
print("Oh, no!!!")
return
}
let json = JSON(object)
if let jArray = json.array {
if let westHolidayArray = jArray[0]["schedule"].array {
for train in westHolidayArray {
if let time = train["departureTime"].string,
let dest = train["destination"].string,
let type = train["trainType"].string {
let dict = ["time":time, "dest":dest, "type": type]
self.scheduleArray.append(d)
}
}
}
}
}
}
}
First of all you should create a class that is your model of Schedule like this
class Schedule: NSObject {
var departureTime: String
var destination: String
var trainType: String
init(jsonDic : NSDictionary) {
self.departureTime = jsonDic["departureTime"] != nil ? jsonDic["departureTime"] as! String! : nil
self.destination = jsonDic["destination"] != nil ? jsonDic["destination"] as! String! : nil
self.trainType = jsonDic["trainType"] != nil ? jsonDic["trainType"] as! String : nil
}
}
And in your view controller your going to need an array of the Schedule object and after you could parse your Json you do it like this:
class ScheduleController: UIViewController {
// The two object use to show the spinner loading
var loadingView: UIView = UIView()
var spinner = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
// Array of your objects
var arrSchedule: [Schedule] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.getInfoSchedule()
}
func getInfoSchedule() {
showActivityIndicator()
Alamofire.request("https://api.mynexttrainschedule.net/", method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseJSON {
response in
self.hideActivityIndicator()
switch response.result {
case .success:
if let objJson = response.result.value as! NSArray? {
for element in objJson {
let data = element as! NSDictionary
if let arraySchedule = data["schedule"] as! NSArray? {
for objSchedule in arraySchedule {
self.arrSchedule.append(Schedule(jsonDic: objSchedule as! NSDictionary))
}
}
}
}
case .failure(let error):
print("Error: \(error)")
}
}
}
//Those two method serves to show a spinner when the request is in execution
func showActivityIndicator() {
DispatchQueue.main.async {
self.loadingView = UIView()
self.loadingView.frame = CGRect(x: 0.0, y: 0.0, width: self.view.frame.width, height: self.view.frame.height)
self.loadingView.center = self.view.center
self.loadingView.backgroundColor = UIColor(rgba: "#111111")
self.loadingView.alpha = 0.9
self.loadingView.clipsToBounds = true
self.spinner = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
self.spinner.frame = CGRect(x: 0.0, y: 0.0, width: 80.0, height: 80.0)
self.spinner.center = CGPoint(x:self.loadingView.bounds.size.width / 2, y:self.loadingView.bounds.size.height / 2)
self.loadingView.addSubview(self.spinner)
self.view.addSubview(self.loadingView)
self.spinner.startAnimating()
}
}
func hideActivityIndicator() {
DispatchQueue.main.async {
self.spinner.stopAnimating()
self.loadingView.removeFromSuperview()
}
}
}
Maybe is not the more efficient way to do it, but it worked for me. I'm using swift3 with xcode 8.1.
Hope it helps !
Basically what you have is an array of schedules. You can map it using ObjectMapper. Install its pod and just create a new Swift file. and Write this
import ObjectMapper
class TrainSchedules: Mappable {
var mySchedules: [Schedules]
required init?(_ map: Map) {
mySchedules = []
}
func mapping(map: Map) {
mySchedules <- map["schedule"]
}
}
class Schedules: Mappable {
var departureTime: String
var destination: String
var trainType: String
required init?(_ map: Map) {
departureTime = ""
destination = ""
trainType = ""
}
func mapping(map: Map) {
departureTime <- map["departureTime"]
destination <- map["destination"]
trainType <- map["trainType"]
}
}
Now you can use it like
if let data = Mapper<TrainSchedules>().map(json){
// now data is an array containt=g all the schedules
// access departureTimelike below
print(data[0].departureTime)
}
I hope it helps, Letme know if you find any difficulty.
Alamofire.request("YOUR_URL", method:.post, parameters:params, encoding:URLEncoding.default, headers: nil).responseJSON { response in
switch(response.result)
{
case .success(_):
if response.result.value != nil
{
let dict :NSDictionary = response.result.value! as! NSDictionary
print(dict)
let status = dict.value(forKey: "status")as! String
print(status)
if(status=="1")
{
self.array_placeRequestId=((dict.value(forKeyPath: "result.request_id") as! NSArray).mutableCopy() as! NSMutableArray)
}
else
{
print("Something Missed")
}
}
break
case .failure(_):
print(response.result.error)
break
}
}