Swift – SwiftData

By | 28/02/2024

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:



Leave a Reply

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