UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Day 77: Problems loading/retrieving data from documents directory in Challenge

Forums > 100 Days of SwiftUI

Hello,

I'm having problems getting data back from documents directory to show it in my List. Saving function seems to be working fine:

func saveData(name: String, id: UUID, image: UIImage) {
        let idString = String("\(id)")
        do {
            try name.write(to: url, atomically: true, encoding: .utf8)
            try idString.write(to: url, atomically: true, encoding: .utf8)
            if let jpegData = image.jpegData(compressionQuality: 0.8) {
                try? jpegData.write(to: url, options: [.atomic, .completeFileProtection])
            }
        } catch {
            print(error.localizedDescription)
        }
    }

However, when i try to loadData, it shows me an error: dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Garbage at end around line 1, column 3." UserInfo={NSDebugDescription=Garbage at end around line 1, column 3., NSJSONSerializationErrorIndex=3})))

This is the loadData function.

func loadData() {
        do {
            let data = try Data(contentsOf: savePath)
            persons = try JSONDecoder().decode([Person].self, from: data)
        } catch {
            print(String(describing: error))
            persons = []
        }
    }

What could be the problem? Does my saving function doesn't save data properly?

Here is my Person structure:

struct Person: Codable, Identifiable, Comparable {
    enum CodingKeys: CodingKey {
        case id, name, image
    }

    let savePath = FileManager.documentsDirectory.appendingPathComponent("ImageContext")

    let id: UUID
    var name: String
    var image: UIImage

    static func < (lhs: Person, rhs: Person) -> Bool {
        lhs.name < rhs.name
    }

    init(id: UUID, name: String, image: UIImage) {
        self.id = id
        self.name = name
        self.image = image
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        id = try container.decode(UUID.self, forKey: .id)
        name = try container.decode(String.self, forKey: .name)

        let imageData = try container.decode(Data.self, forKey: .image)
        let decodedImage = UIImage(data: imageData) ?? UIImage()
        self.image = decodedImage
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(name, forKey: .name)
        if let jpegData = image.jpegData(compressionQuality: 0.8) {
            try? jpegData.write(to: savePath, options: [.atomic, .completeFileProtection])
            try container.encode(jpegData, forKey: .image)
        }
    }
}

2      

@leogc  

It looks like you are not encoding the data before saving. Your save fuction is saving over the same file "url" 3 times.

2      

I think the problem is with your saveData function

func saveData(name: String, id: UUID, image: UIImage) {
        let idString = String("\(id)")
        do {
            try name.write(to: url, atomically: true, encoding: .utf8)
            try idString.write(to: url, atomically: true, encoding: .utf8)
            if let jpegData = image.jpegData(compressionQuality: 0.8) {
                try? jpegData.write(to: url, options: [.atomic, .completeFileProtection])
            }
        } catch {
            print(error.localizedDescription)
        }
}

It looks like you are writing the name and id to the file in UTF8 format, and then converting the image to Data and writing it to the file. But you are not converting any of the data to JSON format. So, when you try to use a JSON decoder in the loadData function, it is expecting the data that it is reading to be in JSON format, and it isn't.

But also, it looks like your saveData function is writing to url and your loadData function is reading from savePath. That might cause another problem.

I would recommend using something like this instead...

func saveData() {
    do {
        let data = try JSONEncoder().encode(persons)
        try data.write(to: savePath, options: [.atomic])
    } catch {
        print("Unable to save data")
    }
}

Since you have already written encode(toEncoder:) and init(fromDecoder:) methods in your Person struct, the struct now conforms to Codable. You have already given the specifics of how to encode and decode your data in those two methods, so shouldn't have to provide those specifics again in your saveData() and loadData() methods. You should just be able to use the JSONEncoder to encode or decode your persons array as needed, since it now conforms to Codable.

2      

@Fly0strich Thank you, the problem with saving function was that i didn't encode it, you're right. But that was not the only thing - i had a lot of unnecessary stuff there which caused to save stuff as dictionary, not array of all people. I commented out the unnecessary stuff and now everything is working fine! :)

Here is the final code that is giving me the expected outcome.

func saveData(/*name: String, id: UUID, image: UIImage*/) {
        // let newPerson = Person(id: id, name: name, image: image)
        do {
            let data = try JSONEncoder().encode(persons)
            try data.write(to: url, options: [.atomic])
            /*if let jpegData = image.jpegData(compressionQuality: 0.8) {
                try? jpegData.write(to: url, options: .atomic)
            }*/
            print("Data saved to URL: \(url)")
        } catch {
            print(error.localizedDescription)
        }
    }
func loadData() {
        do {
            let data = try Data(contentsOf: savePath)
            persons = try JSONDecoder().decode([Person].self, from: data)
        } catch {
            print(String(describing: error))
            persons = []
        }
    }

2      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.