SwiftData Tutorial (Episode I°)

With this post, a short series about SwiftData begins. 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 {
            PomodoriView()
            .tabItem {
                Label("Pomodori", systemImage: "calendar.day.timeline.left")
            }
            ProjectsView()
            .tabItem {
                Label("Projects", systemImage: "list.bullet")
            }
        }
    }
}

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.

The code https://github.com/niqt/SavePomodoroApp

Note: English is not my native language, so I apologize for any errors. I use AI solely to generate the banner of the post; the content is human-generated.

share this post with friends

Picture of Nicola De filippo

Nicola De filippo

I'm a software engineer who adds to the passion for technologies the wisdom and the experience without losing the wonder for the world. I love to create new projects and to help people and teams to improve

1 comment

Leave a comment

Your email address will not be published. Required fields are marked *

Who I am

I'm a software engineer who adds to the passion for technologies the wisdom and the experience without losing the wonder for the world. I love to create new projects and to help people and teams to improve.

Follow Me Here

Get The Latest Updates

Periodically receive my super contents on coding and programming

join the family;)

Recent Posts