The goal of this tutorial is to learn SwiftData by building a Pomodoro app (we’ll be using code from another one of my tutorials). SwiftData replaces CoreData, and I can say that CoreData is a pain for any iOS developer. With SwiftData, things are much simpler. Enough with the introduction, let’s start.
In this post, we’ll cover:
- How to create a project with SwiftData
- How to create entities
- How to create relations between entities
- How to display data
- How to insert data
How do you create a project that needs SwiftData? Simply take a look at the screenshot:

So, in the storage section, choose SwiftData. This will automatically add the necessary code to your …App file. You should have something like this:
import SwiftUI
import SwiftData
@main
struct SavePomodoroAppApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Item.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}
The sharedModelContainer
is the ModelContainer
that is created considering the schema and the configuration. Note that in the configuration, it is possible to define whether we want to store the data only in memory or not.
How do you create an entity? Use the keyword model
:
import Foundation
import SwiftData
@Model
final class ProjectItem: Identifiable {
var name: String
var id = UUID().uuidString
init(name: String) {
self.name = name
}
}
In this case, we want to create an entity Project
that has a name and a UUID identifier (for this reason, we specify the protocol Identifiable
).
Now, how do we create relations between entities? Suppose we want an entity Pomodoro
where every pomodoro has a project:
import Foundation
import SwiftData
@Model
final class PomodoroItem {
var start: Date
var end: Date
var project: ProjectItem
var name: String
init(start: Date, end: Date, project: ProjectItem, name: String) {
self.start = start
self.end = end
self.project = project
self.name = name
}
}
Simply, we add a variable project
of type ProjectItem
.
Now we can change the schema in the …App file:
@main
struct SavePomodoroAppApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
PomodoroItem.self,
ProjectItem.self,
])
. . .
}
How to display data?
First, create the main view for our application:
struct ContentView: View {
var body: some View {
TabView {
Tab("Pomodori", systemImage: "calendar.day.timeline.left") {
PomodoriView()
}
Tab("Projects", systemImage: "list.bullet") {
ProjectsView()
}
}
}
}
So, two tabs: one for Pomodoro and another for Projects.
Take a look at the PomodoriView
to see how to display data:
import SwiftUI
import SwiftData
struct PomodoriView: View {
@Query private var pomodori: [PomodoroItem]
@State var isPresented = false
var body: some View {
NavigationStack {
List {
ForEach(pomodori) { pomodoro in
Text(pomodoro.name)
}
}
.toolbar {
ToolbarItem {
Button(action: {isPresented = true}) {
Label("Add Pomodoro", systemImage: "plus")
}
}
}.sheet(isPresented: $isPresented, content: {
PomodoroView()
})
}
}
}
So, with the Query
, we load all the PomodoroItem
and display them in a list.
The view for the projects is similar:
struct ProjectsView: View {
@Query private var projects: [ProjectItem]
@State var isPresented = false
var body: some View {
NavigationStack {
List {
ForEach(projects) { project in
Text(project.name)
}
}
.toolbar {
ToolbarItem {
Button(action: {isPresented = true}) {
Label("Add Project", systemImage: "plus")
}
}
}.sheet(isPresented: $isPresented, content: {
ProjectView()
})
}
}
}
Both of these views have an add button. By tapping on it, we can insert a project or a pomodoro. Take a look at the project:
import SwiftUI
import SwiftData
struct ProjectView: View {
@Environment(\.modelContext) private var modelContext
@Environment(\.dismiss) private var dismiss
@State var projectName = ""
var body: some View {
VStack {
TextField("Project name", text: $projectName)
Button("Save") {
let newProject = ProjectItem(name: projectName);
modelContext.insert(newProject)
dismiss()
}
}.padding()
}
}
In this view, we declare an environment to call the modelContext
to operate on the data and the dismiss
to close the sheet.
In the action of the save button, we create a ProjectItem
with a name and save it.
The process to create a pomodoro is a bit different:
import SwiftUI
import SwiftData
struct PomodoroView: View {
@Environment(\.modelContext) private var modelContext
@Environment(\.dismiss) private var dismiss
@Query private var projects: [ProjectItem]
@State var selectedProject: ProjectItem?
@State var pomodoroName: String = ""
var body: some View {
VStack {
Picker("Please choose a project", selection: $selectedProject) {
ForEach(projects) { project in
Text(project.name)
.tag(Optional(project))
}
}
TextField("Pomodoro name", text: $pomodoroName)
Button("Start") {
let pomodoro = PomodoroItem(start: Date(), end: Date(), project: selectedProject!, name: pomodoroName)
modelContext.insert(pomodoro)
dismiss()
}
}
}
}
In this case, considering that every pomodoro has a project, we load all the projects and display the project names using a picker. Please note that in the picker, we use the tag with Optional
(because we can select nothing). If we omit this, we get an error: “Picker: the selection ‘nil’ is invalid and does not have an associated tag, this will give undefined results.” If you tap on the picker, nothing happens.
For now, the start button doesn’t start anything, but we simply save the pomodoro by assigning a project name and the current date.

One response to “SwiftData – Episode I°- Introduction”
[…] we start, I advise you to review the previous episodes (SwiftData – Episode I°- Introduction and SwiftData – Episode – II° – […]