When I click one of the items for the first time, it briefly shows the detailed view and then come back. After that, the app seems to freeze. Here’s the scree cap. Notice there is a clipped view at the bottom. What’s going on here?
The way navigation works on an iPhone changed in iOS 15. Add the following modifier to the closing brace of your NavigationView in that code.
.navigationViewStyle(.stack)
Thanks Chris. Unfortunately, that still didn’t solve the problem, I really like to understand what’s going on here. I think it’s a good learning opportunity. FYI, I even copy-pasted all the codes from the resource just to be sure. Could there be something different about this project because I see some words colored differently. Just weird
Can you share your project by compressing it at the root project level (compress the folder which contains your projectName.xcodeproj file and the folder of the same name).
Post that to DropBox and create a share link. Paste that share link in a reply.
OK let me have a look see at what is going on.
(edit). Hmmmm, first question is, what version of Xcode are you using?
OK the issue was with the way you were assigning the UUID for the id
values in the Model. Since id
does not exist as one of the properties of the JSON file then it has to be declared as an optional in the Model. IE:
class Recipe: Identifiable, Decodable {
var id: UUID? // Optional
var name:String
var featured:Bool
var image:String
var description:String
var prepTime:String
var cookTime:String
var totalTime:String
var servings:Int
var highlights:[String]
var ingredients:[Ingredient]
var directions:[String]
}
class Ingredient : Identifiable, Decodable {
var id: UUID? // Optional
var name:String
var num:Int?
var dnom:Int?
var unit:String?
}
Then when you decode the JSON file in your DataService class function getLocalData()
you need to assign that UUID manually after the JSON file has been decoded by looping through the records like this:
let recipeData = try decoder.decode([Recipe].self, from: data)
// Assign UUID to 'id' optional values in the Model for each recipe and each ingredient.
for recipe in recipeData {
recipe.id = UUID()
for ingredient in recipe.ingredients {
ingredient.id = UUID()
}
}
That would have been the last place I"m going to look at since I’ve been using derived property ever since for my model. I’ve always thought the iteration just to assign an ID is inefficient. Now I know a compelling reason not to do that anymore. Thanks so much for all the help!
Specifically, because you implemented id
as a computed property, a new id
was assigned every time the property was accessed. And when a View
gets a new id
, it gets re-rendered. Which means your detail screen would jump back to the list screen being re-rendered.
But you don’t need to make it an optional, you can actually assign it when the object is created. Like so:
class Recipe: Identifiable, Decodable {
let id = UUID()
...
}
Xcode will flag this with a warning that id
can’t be decoded because it’s an immutable property that has an initial value, but it’s a silly warning for perfectly valid code.
And if that warning really bothers you, you can declare a CodingKeys
enum listing all the properties except id
.
I misunderstood the purpose of that feature. I was looking for something as convenient as C# object initializer for readonly property like ID - the object modeler side of me is kicking in I can definitely do a {get;private set} here but too much for its purpose now. Thanks again for the education.