Zubora Code

Creating a Simple Memo App with SwiftUI and SwiftData (Including Data Persistence)

I will outline the steps to create a simple memo app (including data persistence) using SwiftUI and SwiftData.

Published: 8 January, 2024
Revised: 8 January, 2024

Overview

When developing an app with SwiftUI, I found the need for data persistence, so I will summarize the usage by implementing a simple memo app as a sample using SwiftData. While researching persistence in SwiftUI, Core Data frequently appeared, and initially, I intended to use Core Data.

However, when creating the project, I noticed the mention of "SwiftData" in the Storage section. Upon further investigation, it seems to be a newly introduced feature, announced at WWDC 2023. I would like to give it a try since it's a recently released feature.

As a side note, when it comes to new technologies, especially in a professional context, I prefer not to use them until there is sufficient information available. However, for personal projects, I adopt a policy of actively incorporating new technologies.

Overview of the App to Be Created

The goal is to implement the following features in the memo app: creation, display, and deletion of memos.

Source Code

https://github.com/tkugimot/EasyMemo

Summary of the implementation

Add the following files.

ファイル名

役割

Domains/Memo/Memo

Memo Model for SwiftData

Screens/MemoListScreen

This screen displays the source for viewing and deleting memos, as well as transitioning to the creation of new memos.

Views/AddMemoView

This screen allows the addition of new memos.

Implementation

Create project

I will create an app with the Product Name "EasyMemo" and specify SwiftData in the Storage. It will be created with a model called "Item" and a sample implementation that uses it. Please delete them as needed.



Create a model for the data to be saved

import Foundation
import SwiftData

@Model
final class Memo {
    var content: String
    var createdAt: Date
    var updatedAt: Date
    
    init(content: String) {
        self.content = content
        createdAt = Date()
        updatedAt = Date()
    }
}

Create AddMemoView

struct AddMemoView: View {
    @Environment(\.modelContext) private var context
    @Environment(\.presentationMode) var presentation
    
    @State private var content: String = ""
    
    var body: some View {
        VStack {
            Form {
                TextField(
                    text: $content,
                    prompt: Text("Required"),
                    axis: .vertical
                ) {
                    Text("Content")
                }
                .lineLimit(5...10)
            }
        }
        .navigationBarTitle("Add new memo")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button(action: {add()}) {
                    Text("Save")
                }
            }
        }
    }
    
    private func add() {
        let newMemo = Memo(content: content)
        context.insert(newMemo)
        
        // go back to previous page
        presentation.wrappedValue.dismiss()
    }
}

#Preview {
    AddMemoView()
}


Create MemoListScreen

import SwiftUI
import SwiftData

struct MemoListScreen: View {
    @Environment(\.modelContext) private var context
    @Query private var memos: [Memo]
    
    var body: some View {
        NavigationView {
            List {
                ForEach(memos) { memo in
                    NavigationLink {
                        VStack(alignment: .leading) {
                            Text(memo.content)
                                .font(.title)
                                .frame(
                                    minWidth: 0,
                                    maxWidth: .infinity,
                                    minHeight: 0,
                                    maxHeight: .infinity,
                                    alignment: .topLeading)
                                .padding()
                        }
                        .padding()
                    } label: {
                        Text(memo.content)
                            .font(.subheadline)
                            .frame(maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, alignment: .leading)
                            .lineLimit(1)
                    }
                }
                .onDelete(perform: { indexSet in
                    for index in indexSet {
                        delete(memo: memos[index])
                    }
                })
            }
            .navigationTitle("Memo")
            .navigationBarTitleDisplayMode(.automatic)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    NavigationLink(destination: AddMemoView()) {
                        Text("Add")
                    }
                }
            }
        }
    }

    private func delete(memo: Memo) {
        context.delete(memo)
    }
}

#Preview {
    MemoListScreen()
        .modelContainer(for: Memo.self)
}

Modify ContentView

import SwiftUI
import SwiftData

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext

    var body: some View {
        MemoListScreen()
    }
}

#Preview {
    ContentView()
        .modelContainer(for: Memo.self, inMemory: true)
}

Thoughts

Honestly, I was pleasantly surprised by the ease of use of SwiftData. There is virtually no boilerplate code, and I am impressed by its streamlined interface design. The recent Swift language, in general, seems highly implementable and well-organized, giving me a very positive outlook on app development at the moment. Thank you to everyone at Apple for these advancements.

References

Toshimitsu Kugimoto

Software Engineer

Specializing in backend and web frontend development for payment and media at work, the go-to languages for these tasks include Java and TypeScript. Additionally, currently exploring Flutter app development as a side project.