In the last segment, you setup the necessary SwiftData @Models to include Beverages and BakedGoods, both of which are children of the parent Recipe class. The question remains, do you really need to use inheritance?
For this demo, you’ll be picking up in the starter project from where you left it in the last segment.
Before examining the need for inheritance, open the SampleData.swift file and add functions to add sample data for the Beverage and BakedGood classes by adding additional loops to the insertSampleData function:
private func insertSampleData() {
for recipe in Recipe.sampleData {
context.insert(recipe)
}
for bakedGood in BakedGood.sampleBakedGoodData {
context.insert(bakedGood)
}
for beverage in Beverage.sampleBeverageData {
context.insert(beverage)
}
}
You can either ask ChatGPT in Xcode to provide sample data for you, or you can copy it from the final project for this lesson to make it easier for you. I’ll do the latter.
In the init function, don’t forget to add the new @Models from this lesson:
let schema = Schema([
Recipe.self, Ingredient.self, Cookbook.self, BakedGood.self,
Beverage.self
])
Do the same in AppMain.swift:
var body: some Scene {
WindowGroup {
ContentView()
.modelContainer(for: [Recipe.self, Ingredient.self, Cookbook.self,
BakedGood.self, Beverage.self])
}
}
Go to the ContentView.swift file, make sure the canvas is showing, and refresh it if it is paused. The view should now show all the various types of recipes in one combined list, since nothing was done to differentiate them in the query, which simply queries for Recipes.
In this use case, you are not taking advantage of the differences between the Recipe and the child classes. The unique properties in the child classes are not called out, and the fact that there are even different types of recipes is not reflected in the user interface. If this was the final form of your app, there is no need to have the child models, and you could consider everything as Recipes only.
The amount of sample data for the 3 different class types is already pretty big, and as people add more recipes to your app, that value is going to get even bigger. Let’s update the user interface to make it easier for users to find certain types of recipes and take advantage of the model inheritance in the app.
Open up the ContentView.swift file, and in ContentView add a state property to track the currently selected filter type:
@State private var recipeType: RecipeType = .all
Next, make the RecipeType enum above ContentView in the file:
enum RecipeType: String, CaseIterable, Identifiable {
case all = "All"
case bakedGoods = "Baked Goods"
case beverages = "Beverages"
var id: String { self.rawValue }
}
In the body property, above the RecipeListView in the VStack, add a Picker that uses the SegmentedPickerStyle() picker style:
Picker("Recipe Type", selection: $recipeType) {
ForEach(RecipeType.allCases) { recipeType in
Text(recipeType.rawValue)
.tag(recipeType)
}
}
.pickerStyle(SegmentedPickerStyle())
The Picker here takes in a binding that allows you to keep track of the currently selected recipeType. This is done with the help of the tag modifier on the Text view.
Update the canvas if it doesn’t automatically refresh. You should now see a segmented Picker above the list of recipes. Here, the child models, BakedGood and Beverage are called out as filterable types. At this point, however, there is nothing that hooks that picker into the SwiftData models.
So, let’s add a quick and dirty hook into those models, and in the next lesson, you’ll learn how to make those queries a bit more efficient.
Start by adding a @Binding to RecipeListView to hold the RecipeType that is passed in from ContentView.
@Binding var recipeType: RecipeType
Then update the call to RecipeListView in ContentView to pass the binding:
RecipeListView(recipeType: $recipeType)
Then, add @Querys to RecipeListView for the Beverage and BakedGood types.
@Query var beverages: [Beverage]
@Query var bakedGoods: [BakedGood]
These query objects from the model are specifically Beverages or BakedGoods, as opposed to the query for Recipe which fetched all model objects that were recipes regardless of whether they were a child type or not.
Next, replace the for loop in the RecipeListView body with the following code:
switch recipeType {
case .all:
ForEach(recipes) { recipe in
NavigationLink(destination: RecipeDetailView(recipe: recipe)) {
Text(recipe.name)
}
}
.onDelete(perform: deleteRecipes)
case .bakedGoods:
ForEach(bakedGoods) { recipe in
NavigationLink(destination: RecipeDetailView(recipe: recipe)) {
Text(recipe.name)
}
}
.onDelete(perform: deleteRecipes)
case .beverages:
ForEach(beverages) { recipe in
NavigationLink(destination: RecipeDetailView(recipe: recipe)) {
Text(recipe.name)
}
}
.onDelete(perform: deleteRecipes)
}
This code replaces the for loop with a switch that iterates over different query results depending on the current value of recipeType. As mentioned earlier, this is not an ideal piece of code, but it gets us the desired result. Refresh the canvas if it doesn’t automatically, and then switch the picker between the 3 values. The beverage and baked goods types should filter accordingly.
In the next lesson, you’ll learn how to make those queries a bit more efficient.