In this post, we will see how to use SwiftData in our projects.
But, first of all, what is SwiftData?
SwiftData is an Apple’s new data modeling and persistence framework, that offers a streamlined and modern approach to managing data.
Unlike its more complex predecessor, Core Data, SwiftData emphasizes ease of use, type safety, and seamless integration with SwiftUI.
SwiftData allows us to define our data models directly in Swift code, eliminating the need for external model files. We can then effortlessly create, read, update, and delete data objects, while SwiftData intelligently handles the underlying persistence layer for us.
It is important to highlight that currently, there are some current limitations in SwiftData.
Compared to Core Data, it offers less granular control over migrations and complex queries. Additionally, performance optimizations for extremely large-scale applications might benefit more from Core Data’s flexibility.
However, despite these trade-offs, SwiftData is an incredibly powerful and approachable data solution, especially for SwiftUI-based projects.
Let’s now see how to use SwiftData to perform the CRUD operations for an User object, defined as follows:
[USER.SWIFT]
import Foundation
import SwiftData
// The @Model attribute marks the 'User' class as a SwiftData data model
@Model
final class User {
// Marks the 'id' property as an attribute and ensures it's unique for each user
@Attribute(.unique) var id: String = UUID().uuidString
// Stores the user's first name
var name: String
// Stores the user's last name
var surname: String
// The initializer allows for creating 'User' objects with a name and surname
init(name: String, surname: String) {
self.name = name
self.surname = surname
}
}
Now, we have to define the storage area or “container” for our data model within the application.
To achieve it, we have to configure our app:
[TESTSWIFTDATAAPP.SWIFT]
import SwiftUI
import SwiftData
@main
struct TestSwiftDataApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: User.self)
}
}
And now, after defining the Model and Container, we can add the CRUD operations:
INSERT USER
[CONTENTVIEW.SWITF]
import SwiftUI
import SwiftData
struct ContentView: View {
// Access the context from the enviromental
@Environment(\.modelContext) var context
// In SwiftUI view, we can fetch data using the wrapper @Query
@Query private var users: [User]
@State private var showNewUser: Bool = false
@State private var name: String = ""
@State private var surname: String = ""
var body: some View {
NavigationStack
{
List
{
// we show all users
ForEach(users) { user in
VStack(alignment: .leading){
Text("\(user.name) - \(user.surname)")
.font(.headline)
}
}
}
.navigationTitle("Users")
.navigationBarItems(trailing: Button(action: {
showNewUser = true
}, label: {
Image(systemName: "plus.circle")
.imageScale(.large)
}))
.sheet(isPresented: $showNewUser)
{
NewUser()
}
}
}
}
[NEWUSER.SWIFT]
import SwiftUI
import SwiftData
struct NewUser: View {
@Environment (\.presentationMode) var presentationMode
@Environment(\.modelContext) var context
@State var name: String = ""
@State var surname: String = ""
func Save()
{
let newUser = User(name: name, surname: surname)
context.insert(newUser)
presentationMode.wrappedValue.dismiss()
}
var body: some View {
NavigationView {
Form {
// Definition of User Info section
Section{
TextField("Name", text: $name)
TextField("Surname", text: $surname)
}
Button(action: Save) {
Text("ADD USER")
}
.navigationBarTitle(Text("New User"))
}
}
}
}
We have done and now, if we run the application, the following will be the result:
UPDATE USER
[NEWUSER.SWIFT]
import SwiftUI
import SwiftData
struct NewUser: View {
@Environment (\.presentationMode) var presentationMode
@Environment(\.modelContext) var context
@State var name: String = ""
@State var surname: String = ""
@State var id: String = ""
@State var isUpdate: Bool
private var title: String
init(inputName: String, inputSurname: String, inputId: String, isUpdate: Bool) {
_isUpdate = State(initialValue: isUpdate)
if(isUpdate)
{
title = "Modify User"
// we put in name and surname the previous values
_name = State(initialValue: inputName)
_surname = State(initialValue: inputSurname)
_id = State(initialValue: inputId)
}
else
{
title = "New User"
}
}
func Save()
{
let newUser = User(name: name, surname: surname)
if(isUpdate)
{
newUser.id = id
}
context.insert(newUser)
presentationMode.wrappedValue.dismiss()
}
var body: some View {
NavigationView {
Form {
// Definition of User Info section
Section{
TextField("Name", text: $name)
TextField("Surname", text: $surname)
}
Button(action: Save) {
Text(!isUpdate ? "ADD USER" : "MODIFY USER")
}
.navigationBarTitle(Text(self.title))
}
}
}
}
[CONTENTVIEW.SWIFT]
import SwiftUI
import SwiftData
struct ContentView: View {
// Access the context from the enviromental
@Environment(\.modelContext) var context
// In SwiftUI view, we can fetch data using the wrapper @Query
@Query private var users: [User]
@State private var showNewUser: Bool = false
@State private var name: String = ""
@State private var surname: String = ""
@State private var id: String = ""
@State private var isUpdateValue: Bool = false
func UpdateUser(inputUser: User) -> Void
{
// for the update we pass the object User selected
showNewUser = true
name = inputUser.name
surname = inputUser.surname
id = inputUser.id
isUpdateValue = true
}
var body: some View {
NavigationStack
{
List
{
// we show all users
ForEach(users) { user in
HStack
{
VStack(alignment: .leading)
{
Text("\(user.name) - \(user.surname)")
.font(.headline)
}
Spacer()
Button(action: { self.UpdateUser(inputUser: user)})
{
Text("Update")
.foregroundColor(.blue)
}
}
}
}
.navigationTitle("Users")
.navigationBarItems(trailing: Button(action: {
showNewUser = true
}, label: {
Image(systemName: "plus.circle")
.imageScale(.large)
}))
.sheet(isPresented: $showNewUser, onDismiss: {
self.isUpdateValue = false
}) {
NewUser(inputName: name, inputSurname: surname, inputId: id, isUpdate: isUpdateValue)
}
}
}
}
We have done and now, if we run the application, the following will be the result:
DELETE USER
[CONTENTVIEW.SWFT]
import SwiftUI
import SwiftData
struct ContentView: View {
// Access the context from the enviromental
@Environment(\.modelContext) var context
// In SwiftUI view, we can fetch data using the wrapper @Query
@Query private var users: [User]
@State private var showNewUser: Bool = false
@State private var name: String = ""
@State private var surname: String = ""
@State private var id: String = ""
@State private var isUpdateValue: Bool = false
func UpdateUser(inputUser: User) -> Void
{
// for the update we pass the object User selected
showNewUser = true
name = inputUser.name
surname = inputUser.surname
id = inputUser.id
isUpdateValue = true
}
func deleteItem(_ item: User) {
context.delete(item)
}
var body: some View {
NavigationStack
{
List
{
// we show all users
ForEach(users) { user in
HStack
{
VStack(alignment: .leading)
{
Text("\(user.name) - \(user.surname)")
.font(.headline)
}
Spacer()
Button(action: { self.UpdateUser(inputUser: user)})
{
Text("Update")
.foregroundColor(.blue)
}
}
}
.onDelete { indexes in
for index in indexes {
deleteItem(users[index])
}
}
}
.navigationTitle("Users")
.navigationBarItems(trailing: Button(action: {
showNewUser = true
}, label: {
Image(systemName: "plus.circle")
.imageScale(.large)
}))
.sheet(isPresented: $showNewUser, onDismiss: {
self.isUpdateValue = false
}) {
NewUser(inputName: name, inputSurname: surname, inputId: id, isUpdate: isUpdateValue)
}
}
}
}
We have done and now, if we run the application, the following will be the result: