PropertyList Decoder decode returns nil - ios

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.

Related

Why am not able to access my model class in Swift Project?

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.

Got this error: nw_protocol_get_quic_image_block_invoke dlopen libquic failed

I was trying to connect my API data to view it in the cell but it seems that I can't get my response and it's always == nil
The code below describes the Country.SWIFT // Model.SWIFT // Response.SWIFT which is showing how can I get my JSON response using Codable
and CountryCell.SWIFT is showing how I used it to call the API
The link to the API image:
Country.SWIFT
struct Country: Decodable {
var CountryName = ""
var CountryImage = ""
var objectId = ""
// MARK: - Coding Keys
enum CodingKeys: String, CodingKey {
case CountryName = "CountryName"
case CountryImage = "CountryImage"
case objectId = "objectId"
}
//MARK: - Json Decoder
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Parsing our attributes
self.CountryName = try container.decode(String.self, forKey: .CountryName)
self.CountryImage = try container.decode(String.self, forKey: .CountryImage)
self.objectId = try container.decode(String.self, forKey: .objectId)
}
}]
Model.SWIFT
protocol ModelDelegate {
func countriesFetched (_ countries: [Country])
}
class Model {
//MARK: - Vars
var delegate: ModelDelegate?
// MARK: - Get Countries
func getCountries () {
// URL Object
let url = URL(string: Constants.API_URL)
guard url != nil else {return}
// URL Session object
let session = URLSession.shared
//Data Task from URLSession object
let dataTask = session.dataTask(with: url!) { (data, response, error) in
if error != nil || data == nil {
print(error!.localizedDescription)
return
}
print(data!)
do {
let decoder = JSONDecoder()
let response = try decoder.decode(Response.self, from: data!)
if response.items != nil {
DispatchQueue.main.async {
self.delegate?.countriesFetched(response.items!)
}
}
}
catch {
}
}
// start data task
dataTask.resume()
}
}
Response.SWIFT
struct Response: Decodable {
var items: [Country]? = []
init(from decoder: Decoder) throws {
var itemsContrainer = try decoder.unkeyedContainer()
self.items = try itemsContrainer.decode([Country].self)
}
}
CountryCell.SWIFT
class CountryCell: UICollectionViewCell {
//MARK: - Vars
var country: Country?
//MARK: - Outlets
#IBOutlet weak var imageViewCountryOutlet: UIImageView!
#IBOutlet weak var lblCountryNameOutlet: UILabel!
//MARK: - Creating Cell
func generateCell (_ myCountry: Country) {
self.country = myCountry
guard self.country != nil else { return }
lblCountryNameOutlet.text = country!.CountryName
guard self.country!.CountryImage != "" else {return}
let url = URL(string: self.country!.CountryImage)
guard url != nil else {return}
let session = URLSession.shared
let dataTask = session.dataTask(with: url!) { (data, response, error) in
if error == nil || data != nil {
if url!.absoluteString != self.country!.CountryImage {
return
}
let image = UIImage(data: data!)
DispatchQueue.main.async {
self.imageViewCountryOutlet.image = image
}
}
}
dataTask.resume()
}
}
There is no need for the wrapping Response type, decode the list of countries directly:
let decoder = JSONDecoder()
let items = try decoder.decode([Country].self, from: data!)
In your code, when you ask in Response for unkeyedContainer, the expected JSON structure would need additional nested array.
Check decoding errors in the catch block with
do {
...
}
catch {
print(error)
}
I know it sounds bit absurd but in my case, by using class instead of struct and declare all attributes with public var worked for me.

Saving data in Documents Directory in iOS, SwiftUI

I cannot figure out how to save data (multiple properties) in Documents Directory with SwiftUI. I know two variants, 1st - when you have one array property and that works great, the problem here - I don't know how to add additional properties to it. Or maybe it is normal to create about 3 swift-files like this for each property for one project.
1 variant, saving in Documents Directory:
class Stack: ObservableObject {
#Published var cards: [Card]
static let saveKey = "SavedCards"
init() {
self.cards = []
if let data = loadFile() {
if let decoded = try? JSONDecoder().decode([Card].self, from: data) {
self.cards = decoded
return
}
}
}
func add(card: Card) {
cards.insert(card, at: 0)
save()
}
func save() {
if let encoded = try? JSONEncoder().encode(cards) {
saveFile(data: encoded)
}
}
private func saveFile(data: Data) {
let url = getDocumentsDirectory().appendingPathComponent(Self.saveKey)
do {
try data.write(to: url, options: [.atomicWrite, .completeFileProtection])
} catch let error {
print("Could not write data: " + error.localizedDescription)
}
}
func loadFile() -> Data? {
let url = getDocumentsDirectory().appendingPathComponent(Self.saveKey)
if let data = try? Data(contentsOf: url) {
return data
}
return nil
}
private func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
}
The 2nd variant is to use CodingKeys for each property. But I cannot figure out how exactly I can add methods here and use it in other views, to save data from pressing a button for example. It seems like I have to encode and decode in every view over and over again for each change of data. It just seems wrong.
2 variant with Coding Keys
class Profile: Codable, ObservableObject {
enum CodingKeys: CodingKey {
case categories, playedCards, playedRounds
}
#Published var categories: [Category] = [Category.red, .green, .blue, .yellow, .pink, .gray]
#Published var playedCards = 0
#Published var playedRounds = 0
init() { }
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
categories = try container.decode([Category].self, forKey: .categories)
playedCards = try container.decode(Int.self, forKey: .playedCards)
playedRounds = try container.decode(Int.self, forKey: .playedRounds)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(categories, forKey: .categories)
try container.encode(playedCards, forKey: .playedCards)
try container.encode(playedRounds, forKey: .playedRounds)
}
}
So my question is how to use 1 variant with multiple variables. Or if I should use the 2nd variant, how can I "nicely" encode and decode variables from other views.
For now, my solution for 2nd variant is to write standard decode() / encode() methods in ContentView and use it in onDismiss: of Sheet Views to save data from different screens without repetitions.
func loadData() {
let filename = getDocumentsDiretory().appendingPathComponent("SavedData")
do {
let data = try Data(contentsOf: filename)
profile = try JSONDecoder().decode(Profile.self, from: data)
} catch {
print("Unable to load saved profile data")
}
}
func saveData() {
do {
let filename = getDocumentsDiretory().appendingPathComponent("SavedData")
let data = try JSONEncoder().encode(self.profile)
try data.write(to: filename, options: [.atomic, .completeFileProtection])
} catch {
print("Unable to save profile data")
}
}
func getDocumentsDiretory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
.sheet(isPresented: $showingSheet, onDismiss: saveData) {
ProfileView()
}
.onAppear(perform: loadData)

The error from NSKeyedUnarchiver.unarchiveObject to PropertyListDecoder().decode

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)

How to save custom objects that implements Codable

It's now easier with Swift 4 to encode / decode to and from JSON or Properties list.
But I can't find how to encode to Data using Codable, without using Objective-C methods initWithCoder and encodeWithCoder.
Considering this simple class:
struct Question: Codable {
var title: String
var answer: Int
var question: Int
}
How can I encode it to Data using CodingKeys and not initWithCoder and encodeWithCoder?
EDIT:
I also need to be able to deserialize objects previously saved in userdefaults using NSKeyedArchiver.
Well, you no longer need NSKeyedArchiver.
Try this:
let questionObj = Question(title: "WWDC, 2017", answer: 1,question:1)
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(questionObj) {
UserDefaults.standard.set(encoded, forKey: "K_Question")
}
let decoder = JSONDecoder()
if let questionData = UserDefaults.standard.data(forKey: "K_Question"),
let question = try? decoder.decode(Question.self, from: questionData) {
print(question.title)
print(question.answer)
print(question.question)
}
Swift 5: a great simple extension for UserDefaults:
extension UserDefaults {
func save<T: Codable>(_ object: T, forKey key: String) {
let encoder = JSONEncoder()
if let encodedObject = try? encoder.encode(object) {
UserDefaults.standard.set(encodedObject, forKey: key)
UserDefaults.standard.synchronize()
}
}
func getObject<T: Codable>(forKey key: String) -> T? {
if let object = UserDefaults.standard.object(forKey: key) as? Data {
let decoder = JSONDecoder()
if let decodedObject = try? decoder.decode(T.self, from: object) {
return decodedObject
}
}
return nil
}
}
Usage
save
UserDefaults.standard.save(currentUser, forKey: "currentUser")
get
let user: User? = UserDefaults.standard.getObject(forKey: "currentUser")
Well it can be achieved via JSONEncoder and JSONDecoder.
struct Question: Codable {
var title: String
var answer: Int
var question: Int
}
let questionObj = Question(title: "Swift", answer: "Open Source",question:1)
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(questionObj) {
if let json = String(data: encoded, encoding: .utf8) {
print(json)
}
let decoder = JSONDecoder()
if let decoded = try? decoder.decode(Question.self, from: encoded) {
print(decoded)
}
}
struct Question: Codable {
var title: String
var answer: Int
var question: Int
}
class UserDefaults_Question {
static let key = "myapp.trick.question"
static var value: UserDefaults_Question? {
get {
guard let data = UserDefaults.standard.data(forKey: key) else {
print("no model for key: \(key)")
return nil
}
guard let model = try? JSONDecoder().decode(UserDefaults_Question.self, from: data) else {
print("failed to decode model for key: \(key)")
return nil
}
print("did load model for key: \(key)")
return model
}
set {
guard let value = newValue, let data: Data = try? JSONEncoder().encode(value) else {
print("removing model for key: \(key)")
UserDefaults.standard.removeObject(forKey: key)
return
}
print("inserting model for key: \(key)")
UserDefaults.standard.set(data, forKey: key)
}
}
}
UserDefaults_Question.value = Question(title: "Next President", answer: 666, question: -1)

Resources