As we begin Lesson 3 of Module 6 of the CWC+ iOS Databases course, Chris highlights a potential future error for us to address.
We copied our Persistence.swift file from another (new) project but, in order to ensure that the app runs correctly, we need to change a property in the init() method of the Persistence.swift file.
...
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "Recipe Data Model")
if inMemory {
...
The highlighted name
value shown above needs to be the same as the .xcdatamodeId
data file in our project. If it is, we’re good to go.
Parsing JSON into Core Data on first run
The main element of this lesson is to determine if this is the first time the app has been run and, if so, preload our default data into Core Data so that the app has something to work with. Future launches of the app will then just use that which is in Core Data.
The way we do this is as follows:
init() {
checkLoadedData()
}
func checkLoadedData() {
let status = UserDefaults.standard.bool(forKey: Constants.isDataPreloaded)
if status == false {
preloadLocalData()
}
}
func preloadLocalData() {
let localRecipes = DataService.getLocalData()
for r in localRecipes {
let recipe = Recipe(context: managedObjectContext)
recipe.cookTime = r.cookTime
recipe.directions = r.directions
recipe.featured = r.featured
recipe.highlights = r.highlights
recipe.id = UUID()
recipe.image = UIImage(named: r.image)?.jpegData(compressionQuality: 1.0)
recipe.name = r.name
recipe.prepTime = r.prepTime
recipe.servings = r.servings
recipe.summary = r.description
recipe.totalTime = r.totalTime
for i in r.ingredients {
let ingredient = Ingredient(context: managedObjectContext)
ingredient.id = UUID()
ingredient.name = i.name
ingredient.unit = i.unit
ingredient.num = i.num ?? 1
ingredient.denom = i.denom ?? 1
recipe.addToIngredients(ingredient)
}
}
do {
try managedObjectContext.save()
UserDefaults.standard.setValue(true, forKey: Constants.isDataPreloaded)
}
catch {
// Couldn't save to core data
}
}
The above presumes that getLocalData()
is our JSON parser in the DataService
class, and that Recipe
& Ingredient
are entities in our Core Data.
UserDefaults
is a persistent SwiftUI interface that stores key-value pairs across launches of the app (re: Apple Developer Documentation). We’re using it to store our flag constant isDataPreloaded
so that we can check if we’ve been here before. This will prove very useful in future apps.
addToIngredients()
is an “accessor” that was generated by Xcode when we previously set up our Entity relationships, making it easy to, well, to add the ingredients.
A clever piece of code is where the image in the asset library is converted into core data, re:
recipe.image = UIImage(named: r.image)?.jpegData(compressionQuality: 1.0)
For this to work, we need to import SwiftUI
at the top of the file (where we have import Foundation
). Chris does this with import UIKit
but mentions it should be available in SwiftUI, and it is. This is the Apple Developer Documentation page for more info.
Xcode throws a wobbly
On our test run, we get a bunch of Xcode warning errors in the console:
2022-04-29 16:49:21.936301+0100 EnhancedRecipeListApp[23921:392411] [error] fault: One or more models in this application are using transformable properties with transformer names that are either unset, or set to NSKeyedUnarchiveFromDataTransformerName. Please switch to using "NSSecureUnarchiveFromData" or a subclass of NSSecureUnarchiveFromDataTransformer instead. At some point, Core Data will default to using "NSSecureUnarchiveFromData" when nil is specified, and transformable properties containing classes that do not support NSSecureCoding will become unreadable. CoreData: fault: One or more models in this application are using transformable properties with transformer names that are either unset, or set to NSKeyedUnarchiveFromDataTransformerName. Please switch to using "NSSecureUnarchiveFromData" or a subclass of NSSecureUnarchiveFromDataTransformer instead. At some point, Core Data will default to using "NSSecureUnarchiveFromData" when nil is specified, and transformable properties containing classes that do not support NSSecureCoding will become unreadable. 2022-04-29 16:49:21.937309+0100 EnhancedRecipeListApp[23921:392411] [error] CoreData: One or more models in this application are using transformable properties with transformer names that are either unset, or set to NSKeyedUnarchiveFromDataTransformerName. Please switch to using "NSSecureUnarchiveFromData" or a subclass of NSSecureUnarchiveFromDataTransformer instead. At some point, Core Data will default to using "NSSecureUnarchiveFromData" when nil is specified, and transformable properties containing classes that do not support NSSecureCoding will become unreadable. CoreData: warning: Property 'directions' on Entity 'Recipe' is using nil or an insecure NSValueTransformer. Please switch to using "NSSecureUnarchiveFromData" or a subclass of NSSecureUnarchiveFromDataTransformer instead. CoreData: warning: Property 'highlights' on Entity 'Recipe' is using nil or an insecure NSValueTransformer. Please switch to using "NSSecureUnarchiveFromData" or a subclass of NSSecureUnarchiveFromDataTransformer instead.
Chris gets the same messages in the tutorial, but remarks that he doesn’t know what it means. Given the time between his tutorial and today, it’s a surprise that this hasn’t become a bigger issue by now. I’m guessing it will at some point, and it’ll be a job to figure out what to do to fix it. Not a task for a newbie…