Well, it’s getting model inheritance at least! In this article, you’ll get a quick recap on the features of SwiftData in iOS 26, where it used to fall short when dealing with object oriented programming paradigms, and how changes in WWDC 2025 helped resolve those shortcomings. First, let’s quickly go over some object oriented basics.
OOPS, I don’t want to copy code again!
When developing with object oriented languages like Swift and Java, you can associate your class in two different ways: composition and inheritance.
Composition allows you to build your classes out of other classes (and basic types). The composing class has access to any publicly available properties or methods of the composed class, which can be pretty powerful if the APIs are well defined. However, anything with privacy that isn’t public are hidden to the caller. With composition, the composed classes aren’t necessarily related to the composing class, except that one type is used to build, or compose, the other.
Inheritance on the other hand, allows you to construct classes by extending and inheriting properties and methods from a parent class. This hierarchy allow for a more direct connection between classes, with parents passing down properties and functions to their children. This reduces code duplication since you only need to define shared properties and methods in the parent class.
Wait, are you a model?
Since its introduction, SwiftData has allowed developers to compose their SwiftData types out of other types, and even make relationships with the @Relationship macro. This of course represents the composition form of object oriented programming discussed above. To see this in action, open up the starter project for this article. In that project, there is a Cookbook model inside the Cookbook.swift file that is composed of Recipes:
@Model
class Cookbook {
@Relationship var recipes: [Recipe]
init(recipes: [Recipe]) {
self.recipes = recipes
}
}
The Cookbook can access the recipes, but only the public part of the Recipe API, and doesn’t get anything added to its own API for free. If you run the starter project now in the simulator, you’ll the very beginnings of an app that shows all of the recipes.
Inheriting from Past WWDCs
Each year, SwiftData has been adding more and more capabilities, continuing to bring it inline with CoreData. Since some developers prefer to implement broad, generic base classes, and use inheritance to define more specific child classes, SwiftData needed some updates to come inline with modern object oriented practices. Luckily, at WWDC 2025 Apple introduced some great, mostly transparent, additions to SwiftData that provide those updates!
Before we dive in, a quick refresher about how SwiftData is so easy to implement. SwiftData relies heavily on Swift Macros, which hide a lot of implementation details from you, the developer, while providing a lot of guidance to the compiler. The Cookbook example above has a @Model Swift macro before the class declaration. At its most basic level, that is *all* you need to conform a class to SwiftData! Pretty amazing! What about if your class uses inheritance?
Let’s say you have a generic recipe class that you have setup for SwiftData already. In the starter project, take a look at the Recipe.swift file:
@Model
class Recipe {
var title: String
var servings: Int
init(title: String, servings: Int) {
self.title = title
self.servings = servings
}
//... sample data is here
}
This class has a few properties listed here, one for the title, and another for how many servings it makes. If you wanted to specify more specific types of recipes, those would be properties you could repeat, but why do that when you can inherit them!
This year at WWDC, SwiftData was updated to allow for model inheritance when constructing your SwiftData types. This feature is only available in iOS 26 and above, so any uses of this new API have to be annotated with an @available check:
@available(iOS 26, *)
This unfortunately means that if your minimum target is earlier than iOS 26, you won’t able to use model inheritance – so plan accordingly! If using model inheritance is important to your program structure, you will need everyone to upgrade to iOS 26 (or above) before using the next version of your app.
The great thing about the addition of model inheritance is that besides that availability check, for basic usage, you don’t have to do anything special!
If you have a more specific type of Recipe, for example a Beverage, you can use model inheritance to implement it. Make a new file called Beverage.swift and add the following code:
@available(iOS 26, *)
@Model
class Beverage: Recipe {
var caffienated: Bool
init(title: String, servings: Int, caffienated: Bool) {
self.caffienated = caffienated
super.init(title: title, servings: servings)
}
static let sampleBeverageData: [Beverage] = [
Beverage(
title: "Iced Mint Lemonade",
servings: 10,
caffienated: false,
),
Beverage(
title: "Classic Hot Chocolate",
servings: 1,
caffienated: false,
),
Beverage(
title: "Chai Latte",
servings: 1,
caffienated: true,
),
Beverage(
title: "Peach Iced Tea",
servings: 15,
caffienated: false,
)
]
}
The normal @Model Swift Data macro tells the compiler that this is a Swift Data model, but the addition of the @available check signals that the macro has capabilities only available starting in iOS 26, and the Beverage class is inheriting features from the parent model, Recipe. This means that it automatically gets the title and servings properties from the parent Recipe class, without having to repeat those properties here. No code duplication to be found here!
But how does this help us? There is a Picker at the top of the list that currently just has “All”. It might be a good widget to filter the types of recipes. You can add a new Beverage filter type in the ContentView.swift file:
enum RecipeType: String, CaseIterable, Identifiable {
case all = "All"
case beverages = "Beverages"
var id: String { self.rawValue }
}
You can also add in the recipes to the context when the app starts, right below where the recipes are added:
for beverage in Beverage.sampleBeverageData {
modelContainer.mainContext.insert(beverage)
}
Also, don’t forget to add the Beverage model to the available schemas at the top of that file:
let schema = Schema([ Cookbook.self, Recipe.self, Beverage.self ])
If you run the app in the simulator now, you’ll see that all of the recipes show up, but that the picker doesn’t do anything special. This is because some smarts need to be added for the filtering process. See the where to go next section below for next steps here.
The bottom line here is that if you’re using iOS 26 or above, to take advantage of inheritance when building your models, at it most basic, you have to do nothing new besides an @available check. This is the power of Swift Macros in action!
