SwiftData was launched with iOS 17, and is an abstraction layer on top of CoreData, which has been used since the launch in 2005. Just like Apple did with SwiftUI, they’ve made it a lot easier to get started with on-device storage with SwiftData. Building on the new @Observable macro for classes, SwiftData handles your data and manages which data is stored in memory automatically. One big limitation? It requires the user to have iOS 17 or newer.
In this article, I will give you a brief introduction to the bare minimum code you need to get going with SwiftData in your app. This article requires some basic knowledge about SwiftUI and the @Observable macro. If you don’t have experience with either @Observable or ObservableObject, you can read this article from "Hacking with Swift" for a quick introduction.
I've also attempted to collect my favorite resources for learning more about SwiftData, the links are collected with brief descriptions in the bottom of this article. I've even linked to an article on how to combine SwiftData with iCloud storage.
Preparing our data model
Transforming a class from Observable to a SwiftData model is as easy as importing SwiftData and adding a @Model macro to the class.
import SwiftData
@Model class ShoppingListItemModel: Identifiable { // Notice the @Model macro
var id: UUID
var created: Date
var text: String
var checked: Bool
init(id: UUID = UUID(), created: Date = .now, text: String, checked: Bool = false) {
self.id = id
self.created = created
self.text = text
self.checked = checked
}
}
Adding the model container
Now that your data model is prepared for SwiftData, we need to create a model container. The model container is where our database is initialised. You can add multiple containers to multiple parts of your app, which allows you to create multiple databases for each of your models.
If you are interested, you can learn more about how multiple containers work in this timestamped video from Apple. In this example, we will just use one container for our entire app. We can initialise our database by adding the .modelContainer() view modifier to the ContentView:
ContentView()
.modelContainer(for: ShoppingListItemModel.self)
Notice how we pass our model into the container, which tells our container that our database contains only one table; the table for ShoppingListItemModel. We could also add more models to our container by listing multiple models in our .modelContainer() view modifier.
Inserting and deleting from SwiftData
Before we can add or change the data in SwiftData from our view, we need to get our context. No problem! The context is automatically available as an environment object in all children views of where the model container was initialised. If we add:
@Environment(\.modelContext) private var modelContext
in the view where we want to change our data, we have our context available for usage. If you want to read more about what the model context actually is, I believe Apple explains it perfectly.
Now, with our context available, we can start updating our data. If we want to add a new shopping list item, we can just use:
modelContext.insert(ShoppingListItemModel(text: text))
and the data will be updated. One thing to note, is that it won’t automatically be written to disk before we explicitly call:
modelContext.save()
If we want to update an entry, we can just update the object. The object is observable, and your UI will update automatically. Deleting an entry is just as simple, we just use:
modelContext.delete(item)
And remember to save if you want your changes persisted.
Fetching data from SwiftData
When we want to fetch data from SwiftData, we can use the @Query macro directly in our view. We don’t need our context, as Query abstracts away that complexity for us.
@Query private var shoppingList: [ShoppingListItemModel]
If we add the above code to our view we receive all shopping lists in our SwiftData-database, and it also updates the UI automatically when the data changes. It works like a @State with persisted storage!
If we want to sort the data by creation date, we can either do this after querying the data, or we can do it directly in the query for better performance.
@Query(sort: \ShoppingListItemModel.created)
private var shoppingList: [ShoppingListItemModel]
If we want filtering as well, we can create filters with the #Predicate notation. We can for example only get the shopping list items from today:
@Query(
filter: #Predicate {
let startOfDay = Calendar.current.startOfDay(for: Date())
return $0.created >= startOfDay
},
sort: \ShoppingListItemModel.created
) private var shoppingList: [ShoppingListItemModel]
Now you know the basics of applied SwiftData!
Summary
This was an introduction to SwiftData, and hopefully you know enough to start experimenting with it. However, this article was just scratching the surface of what SwiftData is capable of. Check out the articles I’ve listed below to check whether SwiftData has the functionalities that you need for your app.
Merry Christmas! 🎄