Wondering if there is a way to decode some json into a Codable that is a dependency of a View so i can use swiftUI previews?
Here is a working unit test for the codable
func testDecode() {
let data = Data(jsonProj.utf8)
//frmt
let frmt2 = DateFormatter()
frmt2.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX"
//decoder
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
guard let d = frmt2.date(from: dateStr) else { fatalError() } //fixme some danger here
return d
})
do {
let p = try decoder.decode(Project.self, from: data)
XCTAssertEqual(p.id, 2)
print(p)
}
catch {
print(error)
XCTFail()
}
}
I'd like to use it here
struct PreProjectCell_Previews: PreviewProvider {
//static let decodedJson: Project ... NEED THIS PART
static var previews: some View {
NavigationView {
List {
PreProjectCell(project: <#Project#>)
}
}
}
}
extension Project {
static var preview: Self {
let data = Data(jsonProj.utf8)
//frmt
let frmt2 = DateFormatter()
frmt2.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX"
//decoder
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in
let container = try decoder.singleValueContainer()
let dateStr = try container.decode(String.self)
guard let d = frmt2.date(from: dateStr) else { fatalError() } //fixme some danger here
return d
})
return try! decoder.decode(Self.self, from: data)
}
}
Then in your SwiftUI Preview you can do:
struct PreProjectCell_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
List {
PreProjectCell(project: .preview)
}
}
}
}
How to access my Model from ViewController and use the Model data to load in table view????
Source Code Link
My ViewController looks like this
import UIKit
class ViewController: UIViewController {
var cclm: CountryCodeListModel?
override func viewDidLoad() {
super.viewDidLoad()
Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(hello), userInfo: nil, repeats: true)
readLocalJSONFile(forName: "countryList")
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
}
#objc func hello()
{
print(cclm?.data?[0].flag)
}
}
and my model class look like this
struct CountryCodeList : Decodable {
var alpha2Code: String?
var alpha3Code: String?
var flag : String?
var name : String?
var code : String?
}
public struct CountryCodeListModel : Decodable {
var data : [CountryCodeList]?
}
var cclm: CountryCodeListModel?
//Method to load json
func readLocalJSONFile(forName name: String) {
do {
if let filePath = Bundle.main.path(forResource: name, ofType: "json") {
let fileUrl = URL(fileURLWithPath: filePath)
let data = try Data(contentsOf: fileUrl)
if let countryCodeObject = parse(jsonData: data) {
cclm = countryCodeObject
print(cclm?.data?[1].alpha2Code ?? "") //Printing Correct Value
}
}
} catch {
print("error: \(error)")
}
}
func parse(jsonData: Data) -> CountryCodeListModel?{
var dataArray : [Dictionary<String,Any>] = [[:]]
var country = Dictionary<String,Any>()
var modelData = Dictionary<String,Any>()
do {
// make sure this JSON is in the format we expect
if let json = try JSONSerialization.jsonObject(with: jsonData, options: []) as? Dictionary<String,Any> {
dataArray.removeAll()
for item in json["data"] as! [Dictionary<String, Any>] {
country = item
let url = URL(string: country["flag"] as? String ?? "")
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
let image = UIImage(data: data!)
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileName = url?.lastPathComponent // name of the image to be saved
let fileURL = documentsDirectory.appendingPathComponent(fileName ?? "")
if let data = image?.jpegData(compressionQuality: 1.0){
do {
try data.write(to: fileURL)
country["flag"] = fileURL.absoluteString
//print("file saved")
//urlAsString = fileURL.absoluteString
} catch {
print("error saving file:", error)
}
}
dataArray.append(country)
country.removeAll()
}
modelData["data"] = dataArray
//print(modelData)
let jsonData1 = try JSONSerialization.data(withJSONObject: modelData, options: [])
do {
let decodedData = try JSONDecoder().decode(CountryCodeListModel.self, from: jsonData1)
return decodedData
} catch {
print("error: \(error)")
}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
return nil
}
Problem statement:
Iam reading local json and take the url value of flag key and download corresponding images to local. Once i download then am taking the localpath and update in the dictionary and then create JSON object and update my model class.
Now, am trying to access my model class from ViewController like below
print(CountryCodeListModel?.data?[0].name) //check screenshot for error
print(cclm?.data?[0].flag) // this prints nil always
Please check the error screenshots attached2
My JSON look like this
{
"meta":{
"success":true,
"message":"Successfully retrieved country details",
"code":"200"
},
"data":[
{
"alpha2Code":"AF",
"alpha3Code":"AFG",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/afg.png",
"name":"Afghanistan",
"code":"+93"
},
{
"alpha2Code":"AX",
"alpha3Code":"ALA",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/ala.png",
"name":"Aland Islands",
"code":"+358"
},
{
"alpha2Code":"AL",
"alpha3Code":"ALB",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/alb.png",
"name":"Albania",
"code":"+355"
},
{
"alpha2Code":"DZ",
"alpha3Code":"DZA",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/dza.png",
"name":"Algeria",
"code":"+213"
},
{
"alpha2Code":"AS",
"alpha3Code":"ASM",
"flag":"https://raw.githubusercontent.com/DevTides/countries/master/asm.png",
"name":"American Samoa",
"code":"+1684"
}
]
}
You are trying to decode something that doesn't exist.
print(CountryCodeListModel?.data?[0].name) //check screenshot for error
print(cclm?.data?[0].flag) // this prints nil always
The above code states that you want:
the name of
the variable data at position 0 of
the struct CountryCodeListModel.
What you want to do is:
the name of
the variable at position 0 of
an INSTANCE of the struct CountryCodeListModel.
For example...
func readLocalJSONFile(forName name: String) {
do {
if let filePath = Bundle.main.path(forResource: name, ofType: "json") {
let fileUrl = URL(fileURLWithPath: filePath)
let data = try Data(contentsOf: fileUrl)
if let countryCodeObject = parse(jsonData: data) {
cclm = countryCodeObject
print(cclm?.data?[1].alpha2Code ?? "") //Printing Correct Value
print(cclm?.data?[0].flag ?? "")
print(countryCodeObject?.data[0].flag ?? "") // Same as the line above
}
}
} catch {
print("error: \(error)")
}
}
Unless you are trying to use a static variable (at which you would use CountryCodeListModel.data), you need to make sure you are actually using an instance of the structure or an object of a class to reference your properties.
CAVEAT
CountryCodeListModel is a structure. CountryCodeListModel() is an instance of the structure CountryCodeListModel. Since you can have multiple instances of a structure, you need to reference a specific structure when accessing data. Thus, CountryCodeListModel.data will not work and it needs to be CountryCodeListModel().data. In this case, you have cclm.data.
I encode a array of structs. When encoded the returned data has some bytes, which tells me that something was written to the file. But when I decode it returns nil. I don't get any error while decoding. I dont understand why it returns nil after decode.
var allEndpts = [EndPt]()
struct EndPt : Codable {
var contactStruct = ContactStruct()
var purpose: String = String()
}
struct ContactStruct: Codable {
var firstName:String? = nil
var lastName:String? = nil
}
private func saveEndPoints() {
do {
delegate.documentDirectoryUrl = try FileManager.default.url(
for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false
)
let localFileUrl:URL =
delegate.documentDirectoryUrl!
.appendingPathComponent("EndPoints")
UserDefaults.standard.set(localFileUrl, forKey: "localEndPtUrl")
print("localEndPtUrl: \(localFileUrl)")
do {
let encoder = PropertyListEncoder()
let data = try encoder.encode(self.allEndpts)
try data.write(to: localFileUrl)
} catch {
print(error)
}
} catch {
print("error")
}
retrieveFromFile()
}
func retrieveFromFile() {
typealias TempArray = [EndPt]
var temp: TempArray?
let localFileUrl = UserDefaults.standard.url( forKey: "localEndPtUrl")
print("localEndPtUrl: \(localFileUrl)")
do {
let data = try Data(contentsOf: localFileUrl!)
let temp = try PropertyListDecoder().decode(TempArray.self, from: data)
print("EndPt Array Dump: ", temp)
} catch {
print("read error:", error)
}
}
The problem is that
var temp: TempArray?
will always be nil unless you change it. And you never change it. When you say
let temp = try PropertyListDecoder().decode(TempArray.self, from: data)
that is a different temp.
I try to format the code from NSCoding to Codable, the old one using NSKeyedUnarchiver to unarchive.
// ItemStore.swift
var allItems = [Item]()
let itemArchiveURL: URL = {
let documentsDirectories =
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentDirectory = documentsDirectories.first!
return documentDirectory.appendingPathComponent("items.archive")
}()
init() {
if let archivedItems =
NSKeyedUnarchiver.unarchiveObject(withFile: itemArchiveURL.path) as? [Item] {
allItems = archivedItems
}
}
And now I adjust this function to the new one PropertyListDecoder().decode using Codable to unarchive.
init() {
do {
let itemsdata = try Data(contentsOf: itemArchiveURL)
if let archivedItems = try PropertyListDecoder().decode(allItems.self, from: itemsdata) as? [Item] {
allItems = archivedItems
}
}
catch {
print("Error archiving data: \(error)")
}
}
I'm confuse the Xcode report Cannot invoke 'decode' with an argument list of type '([Item], from: Data)' error message, why the Data(contentsOf:) not return the Data value?
Replace
if let archivedItems = try PropertyListDecoder().decode(allItems.self, from: itemsdata) as? [Item] {
With
allItems = try PropertyListDecoder().decode([Item].self, from: itemsdata)
Im calling this api to receive single rocket launch event:
https://launchlibrary.net/1.4/launch/next/1 using simple Get request.
Trying to decode using SwiftyJson (also tried Codable) with lack of success to read the "rocket" -> "imageURL"
here is my code:
struct LaunchHistory {
var launches = [LaunchItem]()
init(with json:JSON) {
for launch in json["launches"].arrayValue {
let launchItem = LaunchItem(with: launch)
launches.append(launchItem)
}
}
}
struct LaunchItem {
let id:Int?
let name: String?
let tbddate: Int?
let status: LaunchStatus?
let rocketImage: String?
init(with json:JSON) {
self.id = json["id"].int
self.name = json["name"].string
self.tbddate = json["tbddate"].int
self.status = LaunchStatus(rawValue: json["status"].int ?? 0)
self.rocketImage = json["rocket"]["imageURL"].string
}
}
when LaunchItem decoded, all i 11 properties/key instead of almost double.
the rocket object is missing.
what am i missing here?
thanks!
It's pretty easy with (De)Codable
struct Root : Decodable {
let launches : [LaunchItem]
}
struct LaunchItem : Decodable {
let id: Int
let name: String
let tbddate: Int
let rocket: Rocket
}
struct Rocket : Decodable {
let imageURL : URL
}
let url = URL(string: "https://launchlibrary.net/1.4/launch/next/1")!
let task = URLSession.shared.dataTask(with: url) { (data, _, error) in
if let error = error { print(error); return }
do {
let result = try JSONDecoder().decode(Root.self, from: data!)
print(result.launches.first?.rocket.imageURL ?? "n/a")
} catch {
print(error)
}
}
task.resume()