0

Fairly new to IOS development. Working on a Recipe keeper app. I have a struct for recipe that holds things like the name, ingredients, instructions. Then I have a class RecipeStore with a published array of Recipes. I am trying to save an instance of RecipeStore in core data so the recipes are saved even when the app is force quit. I have never used Core data and I have done a lot of research but I just don't quite understand it. I tried using userdefaults but it seems that only works with like strings, ints, bools, etc???? Also struggling with some of the core data tutorials I tried following because some of my attributes are arrays. Also if there is a way for me to use userdefaults, please let me know because core data scares me! Any help would be appreciated. Thank you


struct Recipe : Codable, Identifiable {
    var id: String
    var name: String
    var ingredients: [String]
    var instructions: [String]
    var category: String
    var imageName: String
}
class RecipeStore : ObservableObject {
    @Published var recipes: [Recipe]
    init (recipes: [Recipe] = []) {
        self.recipes = recipes

    }
}
1
  • An alternative is to store the data to file for instance by making your struct conform to Codable and save it as json. This also has a learning curve but not as steep as Core Data perhaps. Commented Jun 1, 2022 at 6:49

4 Answers 4

2

My suggestion (inspired by Joakim Danielson) is to save the string arrays as JSON in Core Data. The benefit is that the string can be filtered by predicates.

Declare ingredients and instructions as computed properties to convert the arrays to JSON and vice versa and declare the JSON properties as String

class CDRecipe : NSManagedObject {

    @NSManaged var id: String
    @NSManaged var name: String
    @NSManaged var category: String
    @NSManaged var imageName: String
    @NSManaged var ingredientJSON: String
    @NSManaged var instructionJSON: String
    
    var ingredients : [String] {
        get { decodeArray(from: \.ingredientJSON) }
        set { ingredientJSON = encodeArray(newValue) }
    }
    
    var instructions : [String] {
        get { decodeArray(from: \.instructionJSON) }
        set { instructionJSON = encodeArray(newValue) }
    }
    
    private func decodeArray(from keyPath: KeyPath<CDRecipe,String>) -> [String] {
        return (try? JSONDecoder().decode([String].self, from: Data(self[keyPath: keyPath].utf8))) ?? []
    }
    
    private func encodeArray(_ array: [String]) -> String {
        guard let data = try? JSONEncoder().encode(array) else { return "" }
        return String(data: data, encoding: .utf8)!
    }

}
Sign up to request clarification or add additional context in comments.

4 Comments

Would the CDRecipe class replace my Recipe struct?? Also what do I need to put in my data model file? Would I need to add an entity for CDRecipe?
Yes, a Core Data entity must be a subclass of NSManageObject. You cannot use the struct.
Also, when my app first loads I have a JSON file of recipes so the app is not empty to start off. How would I go about just writing the entire array of Recipes back to the JSON file once a new recipe is added? I have never really worked with JSON files so I am unsure. The idea of overwriting the JSON file once a new recipe is added makes the most sense to me to get this to work the way I want. I could totally be wrong, thats why I am here HA!
The question is about saving a string array in Core Data. To import JSON in Core Data you can adopt Decodable and implement the required method. But this is another question.
0

Friend please refer to this answer Saving an array of objects to Core Data. It explains in well manner. I don't know how are you creating model in core data. But you should create and Recipe model in core data with all its attributes and the use call this function in the class where you are saving your data. Please feel to discuss if you have any question or it does not work. Thanks

func saveRecipeData(_ recipes: [Recipe]) {
let context = appDelegate.persistentContainer.viewContext
for recipe in recipes {
    let newRecipe = NSEntityDescription.insertNewObject(forEntityName: "Recipe", into: context)
    newRecipe.setValue(recipe.id, forKey: "id")
    // Save all the attributes in this way against its respective keys as I have done above.
}
do {
    try context.save()
    print("Success")
} catch {
    print("Error saving: \(error)")
}
}

Comments

0

you can store array of strings in UserDefaults with key that should be store in coredata entity but already you have an property id in your struct so we can use it while storing ingredients and instructions in UserDefaults so create entity with properties :

enter image description here

after select manual/none enter image description here

then create NSManagedCustomClass and add it bellow project bellow any class enter image description here in you extension add two arrays following:

import Foundation
import CoreData


extension CDRecipeStore {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<CDRecipeStore> {
        return NSFetchRequest<CDRecipeStore>(entityName: "CDRecipeStore")
    }

    @NSManaged public var id: String?
    @NSManaged public var name: String?
    @NSManaged public var category: String?
    @NSManaged public var imageName: String?

}

extension CDRecipeStore : Identifiable {

//    return saved Ingredient and instructions Array 1, you can save ingrediant with id at any place in your code

    var ingredients: [String] {
        guard let id = self.id else {return []}
        let savedIngredients = UserDefaults.standard.array(forKey: "\(id + "ing")") as? [String]
        return savedIngredients ?? []
    }
    
    var instructions: [String] {
        guard let id = self.id else {return []}
        let savedInstructions = UserDefaults.standard.array(forKey: "\(id + "inst")") as? [String]
        return savedInstructions ?? []
    }
}

After that when you fetch data from web or firebase you can save it core data by simple save and also save the ingredients and instructions with key id of that object like your fetching function you can call this function it will save ingredients and instruction to UserDefaults:

 func saveCoreData(reciepes:[Recipe]) {
    for reciepe in reciepes {
        let cdRecipe = CDRecipeStore(context: moc)
        cdRecipe.id = reciepe.id
        cdRecipe.category = reciepe.category
        cdRecipe.imageName = reciepe.imageName
        cdRecipe.name = reciepe.name
        UserDefaults.standard.set(reciepe.ingredients, forKey: "\(reciepe.id + "ing")")
        UserDefaults.standard.set(reciepe.instructions, forKey: "\(reciepe.id + "inst")")
        do {
            try? moc.save()
        }
    }
}

after this you can easily retrieve array from coredata custom class as you retrieve other string properties

Comments

0

I see you shared some SwiftUI code and you are new to iOS. Core Data is an expert level framework and integrating it with SwiftUI is not fully understood yet. Instead, I would recommend going the document-based route which does have some native support inside SwiftUI. It would work well with your existing struct model. It's explained the video Build Document Based Apps in SwiftUI (WWDC 2021). And here is a sample project Building a Document-Based App with SwiftUI (Sample Code).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.