Search code examples
jsonswiftcore-dataswiftui

Saving changes from a loaded json File to Core Data in SwiftUI


i want to load my data with a json file. So far so good. But now I am struggling. What if the user made some changes to the Oil Object and want to save them? My idea was, that i save the changed oils object to CoreData. But what is this possible? Because every time the user launches the app, the untouched json file gets loaded and the user will not see his changed objects. How can i handle that? Or is my thinking wrong?

struct Oil: Codable, Hashable, Identifiable {
    var id: Int
    
    let image: String
    let color: String
    
    let title: String
    let subtitle: String
    let description: String
    
    var localizedTitle: LocalizedStringKey {
        return LocalizedStringKey(title)
    }
    var localizedDescription: LocalizedStringKey {
        return LocalizedStringKey(description)
    }
    
    var isFavorite: Bool
    
    static let exampleOil = Oil(id: 10001, image: "",color: "lavenderColor" ,title: "lavender", subtitle: "Lavandula angustifolia", description: "", isFavorite: false)
    
}

final class Oils: ObservableObject {
    
    var oils: [Oil] = load("oilDatabase.json")
}


func load<T: Decodable>(_ filename: String) -> T {
    let data: Data

    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
    else {
        fatalError("Couldn't find \(filename) in main bundle.")
    }

    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }

    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}

Solution

  • Do you really need Core Data? If you are just storing a single json file you could save yourself a lot of complexity and just write the updated version of the file to the documents directory.

    All you would have to do is to check that the file exists in the documents directory, if it does load it from there otherwise load it from your bundle.

    This gets the URL for the documents directory

    func getDocumentsDirectory() -> URL {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return paths[0]
    }
    

    Then we just need to update your load to check that the file exists in the documents directory before loading it.

    func load<T: Decodable>(_ filename: String) -> T {
        let data: Data
    
        // Check that the fileExists in the documents directory
        let filemanager = FileManager.default
        let localPath = getDocumentsDirectory().appendingPathComponent(filename)
    
        if filemanager.fileExists(atPath: localPath.path) {
            
            do {
                data = try Data(contentsOf: localPath)
            } catch {
               fatalError("Couldn't load \(filename) from documents directory:\n\(error)")
            }
    
        } else {
            // If the file doesn't exist in the documents directory load it from the bundle
            guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
            else {
                fatalError("Couldn't find \(filename) in main bundle.")
            }
    
            do {
                data = try Data(contentsOf: file)
            } catch {
                fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
            }
        }
    
    
        do {
            let decoder = JSONDecoder()
            return try decoder.decode(T.self, from: data)
        } catch {
            fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
        }
    }
    

    You will also need to save any changes that you make to the json. You can do that with the following function.

    func save<T: Encodable>(_ filename: String, item: T) {
    
        let encoder = JSONEncoder()
        do {
            let url = getDocumentsDirectory().appendingPathComponent(filename)
            let encoded = try encoder.encode(item)
            let jsonString = String(data: encoded, encoding: .utf8)
            try jsonString?.write(to: url, atomically: true, encoding: .utf8)
        } catch {
            // handle your error
        }
    }