Building a Notes App with Core Data or SwiftData
Course: iOS App Development
Topic: Building a Notes App with Core Data or SwiftData
Course Link: iOS Training at JustAcademy
Demo Link: Register for a Free Course Demo
Website: JustAcademy Official Website
1. Introduction
A Notes App is a practical iOS application that allows users to create, view, edit, search, and delete personal notes. Building this application is an excellent way to understand local data persistence, database operations, SwiftUI interfaces, and application architecture.
In iOS development, data can be stored locally using Apple's persistence technologies. Two important frameworks are Core Data and SwiftData.
- Core Data: Apple's established object graph and persistence framework. It provides entities, managed objects, contexts, fetch requests, relationships, and migration capabilities.
- SwiftData: Apple's modern persistence framework that uses Swift macros and integrates closely with SwiftUI. It provides a more direct way to define models and manage persistent data.
Both frameworks can be used to build a Notes App. The choice depends on the application's deployment requirements, existing architecture, and development needs.
2. Learning Objectives
- Understand local data persistence in iOS applications.
- Learn the differences between Core Data and SwiftData.
- Design a Note data model.
- Create, read, update, and delete notes.
- Build a Notes App interface using SwiftUI.
- Use Core Data managed objects and managed object contexts.
- Use SwiftData models, ModelContainer, and ModelContext.
- Implement search, sorting, and filtering.
- Handle persistence errors and data changes.
- Test the Notes App and improve its user experience.
3. Understanding Local Data Persistence
Data persistence means storing information so that it remains available even after an application is closed or the device is restarted.
Without persistence, notes stored only in temporary variables may disappear when the application terminates. A persistence framework stores the information locally so the application can retrieve it later.
3.1 Example Without Persistence
var notes = ["My first note", "Learn SwiftUI"]
This array stores information in application memory. Unless the information is saved separately, it does not provide reliable long-term storage.
3.2 Example With Persistence
When a user creates a note, the application saves its title, content, and creation date to a persistent store. When the application opens again, it fetches the saved records and displays them.
User creates a note
|
v
Application receives input
|
v
Create a Note model
|
v
Save through Core Data or SwiftData
|
v
Persistent local storage
|
v
Fetch saved notes when required
4. Understanding Core Data
Core Data is an Apple framework used to manage an application's model layer and persist data. It provides an object-oriented approach to storing and retrieving structured information.
Core Data is not simply a database wrapper. It manages an object graph, tracks changes to managed objects, supports relationships, and coordinates persistence through managed object contexts.
4.1 Important Core Data Components
| Component |
Description |
| NSPersistentContainer |
Sets up and coordinates the Core Data stack. |
| NSManagedObjectModel |
Describes the application's data entities and relationships. |
| NSManagedObjectContext |
Manages objects, tracks changes, and coordinates saving. |
| NSPersistentStoreCoordinator |
Coordinates access between contexts and persistent stores. |
| NSManagedObject |
Represents an individual managed model object. |
| NSFetchRequest |
Describes which objects should be retrieved. |
| NSPersistentStore |
Represents the underlying persistent storage. |
4.2 Core Data Architecture
SwiftUI View
|
v
NSManagedObjectContext
|
v
NSPersistentStoreCoordinator
|
v
Persistent Store
|
v
Local Application Data
The view interacts with managed objects through a context. The context tracks changes and coordinates with the persistent store when data is saved or fetched.
5. Understanding SwiftData
SwiftData is Apple's modern persistence framework that integrates with Swift and SwiftUI. It allows developers to describe persistent models using the @Model macro and manage them using a model container and model context.
Instead of defining entities through a separate graphical model editor, developers can define model classes directly in Swift.
5.1 Important SwiftData Components
| Component |
Description |
| @Model |
Marks a class as a persistent SwiftData model. |
| ModelContainer |
Manages the model schema and persistent storage. |
| ModelContext |
Inserts, fetches, updates, deletes, and saves model instances. |
| @Query |
Fetches persistent model objects for SwiftUI views. |
| FetchDescriptor |
Defines fetch conditions and sorting rules. |
| @Attribute |
Customizes the behavior of model attributes. |
| @Relationship |
Defines relationships between persistent models. |
5.2 SwiftData Architecture
SwiftUI View
|
v
@Query / ModelContext
|
v
ModelContainer
|
v
SwiftData Persistence
|
v
Local Application Storage
6. Core Data vs SwiftData
| Feature |
Core Data |
SwiftData |
| Introduction |
Established Apple persistence framework |
Modern Swift-native persistence framework |
| Model Definition |
Entities and NSManagedObject subclasses |
Swift classes using @Model |
| Data Context |
NSManagedObjectContext |
ModelContext |
| Storage Management |
NSPersistentContainer and related stack components |
ModelContainer |
| Fetching |
NSFetchRequest |
@Query and FetchDescriptor |
| SwiftUI Integration |
Supported through property wrappers and context integration |
Direct integration through @Query and environment values |
| Migration |
Supports lightweight and custom migration strategies |
Supports automatic and planned schema migration |
| Legacy Projects |
Suitable for established Core Data applications |
Can be adopted in suitable existing projects |
| Platform Requirements |
Supports a broad range of Apple platform versions |
Available from iOS 17 and corresponding platform releases |
6.1 Which Framework Should You Choose?
- Choose SwiftData when building a modern SwiftUI application with a suitable minimum deployment target.
- Choose Core Data when supporting older iOS versions or maintaining an existing Core Data application.
- Consider Core Data when an existing project depends on advanced Core Data features or established persistence infrastructure.
- Evaluate migration, relationships, testing, and synchronization requirements before choosing a framework.
7. Planning the Notes App
Before writing code, define the main features and data requirements of the application.
7.1 Main Features
- Create a new note.
- Display all saved notes.
- Open and read a note.
- Edit an existing note.
- Delete a note.
- Search notes by title or content.
- Sort notes by creation or modification date.
- Display an empty state when no notes exist.
- Preserve notes after the application is closed.
7.2 Note Data Model
Each note can contain the following properties:
| Property |
Data Type |
Purpose |
| id |
UUID |
Unique identity of the note |
| title |
String |
Title of the note |
| content |
String |
Main note content |
| createdAt |
Date |
Creation date |
| updatedAt |
Date |
Last modification date |
| isPinned |
Boolean |
Indicates whether the note is pinned |
8. Building the Notes App Using SwiftData
This section demonstrates a basic Notes App using SwiftUI and SwiftData. The example covers model creation, storage configuration, listing, adding, editing, searching, and deleting notes.
Requirement: SwiftData is available from iOS 17.0. Use a suitable Xcode version and iOS deployment target.
8.1 Step 1: Create an Xcode Project
- Open Xcode.
- Select File and choose New Project.
- Select the iOS App template.
- Enter the product name as
NotesApp.
- Select Swift as the programming language.
- Select SwiftUI as the interface.
- Set the deployment target to iOS 17.0 or later.
- Create the project.
8.2 Step 2: Import Required Frameworks
The application uses SwiftUI to build its interface and SwiftData to manage persistent notes.
import SwiftUI
import SwiftData
8.3 Step 3: Create the Note Model
Create a new Swift file named Note.swift. Define a model class and mark it with the @Model macro.
import Foundation
import SwiftData
@Model
final class Note {
var id: UUID
var title: String
var content: String
var createdAt: Date
var updatedAt: Date
var isPinned: Bool
init(
title: String,
content: String,
isPinned: Bool = false
) {
self.id = UUID()
self.title = title
self.content = content
self.createdAt = Date()
self.updatedAt = Date()
self.isPinned = isPinned
}
}
Explanation
@Model makes the class persistable through SwiftData.
id provides a unique identifier for each note.
title stores the note heading.
content stores the note body.
createdAt records when the note was created.
updatedAt records the latest modification date.
isPinned indicates whether the note should appear as pinned.
8.4 Step 4: Configure the Model Container
SwiftData requires a ModelContainer to manage the model schema and persistent storage. Add the container to the application entry point.
import SwiftUI
import SwiftData
@main
struct NotesApp: App {
var body: some Scene {
WindowGroup {
NotesListView()
}
.modelContainer(for: Note.self)
}
}
The modelContainer(for:) modifier configures persistence for the Note model and makes the associated model context available to SwiftUI views.
8.5 Step 5: Create the Notes List Screen
Create a file named NotesListView.swift. Use @Query to fetch saved notes and automatically keep the displayed list synchronized with changes to the persistent data.
import SwiftUI
import SwiftData
struct NotesListView: View {
@Environment(\.modelContext)
private var modelContext
@Query(sort: \Note.updatedAt, order: .reverse)
private var notes: [Note]
@State private var searchText = ""
@State private var showingEditor = false
private var filteredNotes: [Note] {
guard !searchText.isEmpty else {
return notes
}
return notes.filter {
$0.title.localizedCaseInsensitiveContains(searchText) ||
$0.content.localizedCaseInsensitiveContains(searchText)
}
}
var body: some View {
NavigationStack {
Group {
if filteredNotes.isEmpty {
ContentUnavailableView(
"No Notes Found",
systemImage: "note.text",
description: Text(
searchText.isEmpty
? "Create your first note."
: "Try a different search term."
)
)
} else {
List {
ForEach(filteredNotes) { note in
NavigationLink {
NoteEditorView(note: note)
} label: {
VStack(alignment: .leading, spacing: 6) {
HStack {
Text(note.title)
.font(.headline)
if note.isPinned {
Image(systemName: "pin.fill")
.foregroundStyle(.orange)
}
}
Text(note.content)
.font(.subheadline)
.foregroundStyle(.secondary)
.lineLimit(2)
Text(
note.updatedAt,
style: .date
)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
.onDelete(perform: deleteNotes)
}
}
}
.navigationTitle("My Notes")
.searchable(text: $searchText)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
showingEditor = true
} label: {
Image(systemName: "plus")
}
}
}
.sheet(isPresented: $showingEditor) {
NoteEditorView(note: nil)
}
}
}
private func deleteNotes(at offsets: IndexSet) {
let selectedNotes = offsets.map {
filteredNotes[$0]
}
for note in selectedNotes {
modelContext.delete(note)
}
do {
try modelContext.save()
} catch {
print("Unable to delete notes: \(error)")
}
}
}
Explanation
@Environment(\.modelContext) provides access to the current SwiftData context.
@Query retrieves saved Note objects.
@State manages temporary interface state such as search text and sheet presentation.
NavigationStack manages navigation between the notes list and editor.
searchable adds a search interface.
onDelete provides a deletion action for list rows.
modelContext.delete() marks the selected note for deletion.
modelContext.save() explicitly saves the context changes.
8.6 Step 6: Create the Note Editor Screen
Create a file named NoteEditorView.swift. This view supports creating a new note and editing an existing note.
import SwiftUI
import SwiftData
struct NoteEditorView: View {
@Environment(\.modelContext)
private var modelContext
@Environment(\.dismiss)
private var dismiss
let note: Note?
@State private var title = ""
@State private var content = ""
@State private var isPinned = false
var body: some View {
NavigationStack {
Form {
Section("Note Details") {
TextField("Enter note title", text: $title)
Toggle("Pin this note", isOn: $isPinned)
}
Section("Content") {
TextEditor(text: $content)
.frame(minHeight: 220)
}
}
.navigationTitle(note == nil ? "New Note" : "Edit Note")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
dismiss()
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
saveNote()
}
.disabled(
title.trimmingCharacters(
in: .whitespacesAndNewlines
).isEmpty
)
}
}
.onAppear {
loadExistingNote()
}
}
}
private func loadExistingNote() {
guard let note else {
return
}
title = note.title
content = note.content
isPinned = note.isPinned
}
private func saveNote() {
let cleanTitle = title.trimmingCharacters(
in: .whitespacesAndNewlines
)
guard !cleanTitle.isEmpty else {
return
}
if let note {
note.title = cleanTitle
note.content = content
note.isPinned = isPinned
note.updatedAt = Date()
} else {
let newNote = Note(
title: cleanTitle,
content: content,
isPinned: isPinned
)
modelContext.insert(newNote)
}
do {
try modelContext.save()
dismiss()
} catch {
print("Unable to save note: \(error)")
}
}
}
Explanation
- The optional
note parameter determines whether the screen creates or edits a note.
@State stores the values entered in the form.
loadExistingNote() fills the editor when an existing note is selected.
saveNote() validates the title and saves the changes.
modelContext.insert() adds a new note to the persistence context.
- Updating the properties of an existing model allows SwiftData to track the changes.
dismiss() closes the editor after a successful save.
9. Implementing CRUD Operations with SwiftData
CRUD stands for Create, Read, Update, and Delete. These are the four basic operations used to manage application data.
9.1 Create
Creating a note means generating a new Note model instance and inserting it into the model context.
let note = Note(
title: "SwiftData Learning",
content: "Learn models and persistence."
)
modelContext.insert(note)
try modelContext.save()
9.2 Read
Reading means fetching saved records. In SwiftUI, @Query can automatically fetch and observe model changes.
@Query(sort: \Note.createdAt, order: .reverse)
private var notes: [Note]
9.3 Update
Updating means changing the properties of an existing model object.
note.title = "Updated Title"
note.content = "Updated note content."
note.updatedAt = Date()
try modelContext.save()
9.4 Delete
Deleting means removing a model instance from the persistence context.
modelContext.delete(note)
try modelContext.save()
10. Searching and Sorting Notes
Search and sorting make the Notes App easier to use when the user has stored many records.
10.1 Searching Notes
The application can search both the title and content of a note.
let matchingNotes = notes.filter {
$0.title.localizedCaseInsensitiveContains(searchText) ||
$0.content.localizedCaseInsensitiveContains(searchText)
}
For larger datasets, consider using a database-level predicate or fetch descriptor instead of filtering the entire collection in memory.
10.2 Sorting by Latest Update
@Query(sort: \Note.updatedAt, order: .reverse)
private var notes: [Note]
This displays recently modified notes first.
10.3 Sorting by Creation Date
@Query(sort: \Note.createdAt, order: .forward)
private var notes: [Note]
This displays older notes before newer notes.
11. Building the Notes App Using Core Data
Core Data provides an alternative implementation for applications that need its established persistence architecture or support older iOS deployment targets.
11.1 Step 1: Enable Core Data
- Open Xcode and create an iOS application project.
- Choose SwiftUI as the interface.
- Select the option to use Core Data if it is available in the project template.
- Create the project.
- Verify that the project contains a
.xcdatamodeld file.
11.2 Step 2: Create the Note Entity
Open the Core Data model editor and create an entity named NoteEntity.
Add the following attributes:
| Attribute |
Type |
Optional |
| id |
UUID |
No |
| title |
String |
No |
| content |
String |
Yes, if desired |
| createdAt |
Date |
No |
| updatedAt |
Date |
No |
| isPinned |
Boolean |
No |
For the Boolean attribute, provide an appropriate default value, such as false. Configure the entity's class generation according to the project's Core Data code-generation settings.
11.3 Step 3: Configure the Persistent Container
A typical Core Data application uses NSPersistentContainer to initialize and manage its persistence stack.
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(
name: "NotesModel"
)
if inMemory {
container.persistentStoreDescriptions.first?
.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores { _, error in
if let error = error as NSError? {
fatalError(
"Core Data store error: \(error), \(error.userInfo)"
)
}
}
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
Important: Replace NotesModel with the actual name of your Core Data model file without its extension.
11.4 Step 4: Connect Core Data to the SwiftUI App
import SwiftUI
import CoreData
@main
struct NotesCoreDataApp: App {
let persistenceController =
PersistenceController.shared
var body: some Scene {
WindowGroup {
CoreDataNotesListView()
.environment(
\.managedObjectContext,
persistenceController.container.viewContext
)
}
}
}
11.5 Step 5: Fetch Notes Using @FetchRequest
SwiftUI provides @FetchRequest to retrieve Core Data objects and keep the view synchronized with changes in the managed object context.
import SwiftUI
import CoreData
struct CoreDataNotesListView: View {
@Environment(\.managedObjectContext)
private var context
@FetchRequest(
sortDescriptors: [
NSSortDescriptor(
keyPath: \NoteEntity.updatedAt,
ascending: false
)
],
animation: .default
)
private var notes: FetchedResults<NoteEntity>
var body: some View {
NavigationStack {
List {
ForEach(notes, id: \.objectID) { note in
VStack(alignment: .leading) {
Text(note.title ?? "Untitled")
.font(.headline)
Text(note.content ?? "")
.font(.subheadline)
.foregroundStyle(.secondary)
.lineLimit(2)
}
}
.onDelete(perform: deleteNotes)
}
.navigationTitle("My Notes")
}
}
private func deleteNotes(at offsets: IndexSet) {
for index in offsets {
context.delete(notes[index])
}
do {
try context.save()
} catch {
print("Delete error: \(error)")
}
}
}
11.6 Step 6: Insert a New Note
To create a new note, create an instance of the generated managed object class, assign its properties, and save the context.
func createNote(
title: String,
content: String,
context: NSManagedObjectContext
) {
let newNote = NoteEntity(context: context)
newNote.id = UUID()
newNote.title = title
newNote.content = content
newNote.createdAt = Date()
newNote.updatedAt = Date()
newNote.isPinned = false
do {
try context.save()
} catch {
context.rollback()
print("Unable to save note: \(error)")
}
}
11.7 Step 7: Update an Existing Note
func updateNote(
note: NoteEntity,
title: String,
content: String,
context: NSManagedObjectContext
) {
note.title = title
note.content = content
note.updatedAt = Date()
do {
try context.save()
} catch {
context.rollback()
print("Unable to update note: \(error)")
}
}
11.8 Step 8: Delete a Note
func deleteNote(
note: NoteEntity,
context: NSManagedObjectContext
) {
context.delete(note)
do {
try context.save()
} catch {
context.rollback()
print("Unable to delete note: \(error)")
}
}
12. Understanding Core Data Fetch Requests
A fetch request describes the data that should be retrieved from the persistent store.
12.1 Basic Fetch Request
let request: NSFetchRequest<NoteEntity> =
NoteEntity.fetchRequest()
do {
let notes = try context.fetch(request)
print("Total notes: \(notes.count)")
} catch {
print("Fetch failed: \(error)")
}
12.2 Filtering Notes
Use an NSPredicate to retrieve notes matching a specific condition.
let request: NSFetchRequest<NoteEntity> =
NoteEntity.fetchRequest()
request.predicate = NSPredicate(
format: "title CONTAINS[cd] %@",
"Swift"
)
do {
let results = try context.fetch(request)
print("Matching notes: \(results.count)")
} catch {
print("Search failed: \(error)")
}
12.3 Sorting Notes
request.sortDescriptors = [
NSSortDescriptor(
key: "updatedAt",
ascending: false
)
]
Sorting by the update date allows recently modified notes to appear first.
13. Adding Note Categories and Relationships
A more advanced Notes App can organize notes into categories such as Personal, Work, Study, and Projects.
This introduces the concept of relationships between data models.
13.1 SwiftData Category Model
import Foundation
import SwiftData
@Model
final class NoteCategory {
var name: String
@Relationship(deleteRule: .nullify)
var notes: [Note] = []
init(name: String) {
self.name = name
}
}
A category can contain multiple notes. The relationship configuration determines how related notes are handled when a category is deleted.
13.2 Relationship Concepts
| Relationship |
Description |
Example |
| One-to-One |
One object relates to one other object. |
Profile and user settings |
| One-to-Many |
One object relates to multiple objects. |
Category and notes |
| Many-to-Many |
Multiple objects relate to multiple objects. |
Notes and tags |
14. Handling Data Validation
Validation helps prevent incomplete or invalid records from being stored.
For a Notes App, useful validation rules include:
- Do not allow a note with an empty title.
- Trim unnecessary whitespace from the title.
- Allow an empty body only if the application design supports it.
- Set creation and modification dates automatically.
- Limit unusually large text input if the product requires it.
- Display a helpful message when saving fails.
Example Title Validation
func validateTitle(_ title: String) -> Bool {
let cleanedTitle = title.trimmingCharacters(
in: .whitespacesAndNewlines
)
return !cleanedTitle.isEmpty
}
15. Managing Persistence Errors
Database operations can fail for different reasons, including storage problems, invalid model configurations, migration issues, or unavailable resources.
Applications should handle errors gracefully instead of silently losing user data.
Recommended Error-Handling Practices
- Use
do-catch around operations that can throw errors.
- Display an appropriate error message when saving fails.
- Avoid dismissing the editor before a save succeeds.
- Keep user-entered content available when an operation fails.
- Use logging to investigate unexpected persistence problems.
- Test error scenarios with different storage configurations.
16. Data Migration and Schema Changes
As an application grows, its data model may change. For example, a Notes App may initially store only a title and content, but later introduce categories, tags, attachments, or reminder dates.
Changing a persistent data model requires a migration strategy so that existing user information remains usable.
16.1 Core Data Migration
Core Data supports lightweight migration for many common model changes. More complex changes may require a mapping model or a custom migration approach.
16.2 SwiftData Migration
SwiftData supports automatic migration for compatible schema changes. For more complex changes, developers can define versioned schemas and a SchemaMigrationPlan.
Migration Best Practices
- Keep a record of model changes between releases.
- Test migrations using realistic sample data.
- Back up important data where appropriate.
- Avoid deleting persistent stores as a shortcut for migration problems.
- Test both fresh installations and upgrades from older versions.
17. Testing the Notes App
Testing confirms that notes are stored correctly and that the application behaves consistently across different operations.
17.1 Functional Test Cases
| Test Case |
Action |
Expected Result |
| Create Note |
Enter a title and content and tap Save. |
The new note appears in the list. |
| Read Note |
Select an existing note. |
The correct note details appear. |
| Update Note |
Change the title or content. |
The updated values are displayed. |
| Delete Note |
Delete a selected note. |
The note disappears from the list. |
| Search Note |
Enter a keyword. |
Matching notes are displayed. |
| Empty Title |
Try saving without a title. |
The application prevents invalid submission. |
| Persistence |
Close and reopen the application. |
Previously saved notes remain available. |
| Sorting |
Modify an older note. |
It appears in the correct sorted position. |
17.2 Testing Persistence
- Create multiple notes.
- Close the application.
- Launch the application again.
- Verify that all saved notes are displayed.
- Update an existing note.
- Restart the application and verify the updated content.
- Delete a note and confirm that it remains deleted after relaunching.
18. Performance and Data Management Best Practices
- Use appropriate fetch strategies: Avoid loading unnecessarily large datasets.
- Sort at the persistence layer: Use fetch sorting for large collections where appropriate.
- Keep UI work responsive: Avoid performing expensive data operations directly on the main thread.
- Use contexts correctly: Respect Core Data context queue confinement and SwiftData concurrency rules.
- Manage relationships carefully: Avoid unnecessary or excessively complex object graphs.
- Handle large notes: Consider appropriate storage and loading strategies for large attachments.
- Test migration: Verify that application upgrades preserve user information.
- Protect private information: Consider device security, data protection, and appropriate backup behavior.
- Use meaningful model names: Keep the data model understandable and maintainable.
19. Common Errors and Troubleshooting
| Problem |
Possible Cause |
Solution |
| Notes disappear after restarting |
Data was stored only in memory or the persistent container was not configured correctly. |
Verify persistent storage configuration and save operations. |
| SwiftData model is not recognized |
Missing framework import or incorrect model declaration. |
Import SwiftData and verify the @Model declaration. |
| Core Data fetch returns no results |
Incorrect entity name, predicate, or persistent store configuration. |
Verify the entity and fetch request configuration. |
| Unable to save changes |
Invalid data, context configuration, or storage error. |
Inspect the thrown error and validate the model. |
| Duplicate notes appear |
Insertion logic is triggered more than once. |
Review button actions and object creation logic. |
| Changes do not appear immediately |
Incorrect context usage or view observation configuration. |
Verify the context and fetch mechanism. |
| Migration error |
Persistent schema changed without a compatible migration. |
Review schema versions and migration strategy. |
| UI becomes slow with many notes |
Too many records are loaded or processed at once. |
Use filtering, sorting, limits, and appropriate fetch strategies. |
20. Practical Project: Complete Notes App
Build a Notes App that allows users to manage personal notes using either SwiftData or Core Data.
Project Requirements
- Create a home screen displaying all notes.
- Build a form for creating new notes.
- Allow users to edit existing notes.
- Provide a delete option.
- Implement search by title and content.
- Sort notes by latest modification date.
- Add a pin option for important notes.
- Store all notes locally.
- Display an empty state when no notes are available.
- Handle validation and persistence errors.
Suggested Project Structure
NotesApp/
|
|-- App/
| |-- NotesApp.swift
|
|-- Models/
| |-- Note.swift
| |-- NoteCategory.swift
|
|-- Views/
| |-- NotesListView.swift
| |-- NoteEditorView.swift
| |-- NoteDetailView.swift
|
|-- Services/
| |-- NotesService.swift
|
|-- Resources/
| |-- Assets.xcassets
|
|-- Tests/
|-- NotesAppTests.swift
This is a suggested structure for organizing a growing project. A small application may begin with fewer files and introduce additional layers as requirements expand.
Expected Project Outcome
At the end of this project, you should have a functional Notes App that can create, display, edit, search, and delete notes while preserving the data between application launches.
21. Practice Exercises
- Create a Note model using SwiftData.
- Configure a ModelContainer in the application entry point.
- Build a SwiftUI list that displays saved notes.
- Implement a form for adding a note.
- Update an existing note and modify its update date.
- Implement note deletion.
- Add a search field that filters notes.
- Sort notes by creation date and modification date.
- Add a pinned-note feature.
- Build a second implementation using Core Data.
- Compare the amount of code required by both frameworks.
- Test persistence after closing and reopening the application.
22. Interview Questions and Answers
Q1. What is Core Data?
Answer: Core Data is Apple's framework for managing an application's object graph and persistent model data. It supports fetching, relationships, change tracking, and data migration.
Q2. What is SwiftData?
Answer: SwiftData is Apple's Swift-native persistence framework that uses macros and integrates with SwiftUI to manage persistent model objects.
Q3. What is the main difference between Core Data and SwiftData?
Answer: Core Data uses managed object models, contexts, and persistent containers, whereas SwiftData uses Swift model classes, the @Model macro, ModelContext, and ModelContainer.
Q4. What is the purpose of the @Model macro?
Answer: The @Model macro makes a Swift class a persistent model that SwiftData can manage.
Q5. What is ModelContainer?
Answer: ModelContainer manages the application's SwiftData schema and persistent storage configuration.
Q6. What is ModelContext?
Answer: ModelContext manages model instances and provides operations to insert, fetch, delete, and save persistent data.
Q7. What is @Query in SwiftData?
Answer: @Query fetches model objects and keeps SwiftUI views synchronized with relevant changes to the fetched data.
Q8. What is NSManagedObjectContext?
Answer: It is a Core Data object that manages a collection of managed objects, tracks their changes, and coordinates saving.
Q9. What is NSPersistentContainer?
Answer: It simplifies Core Data stack setup by coordinating the managed object model, persistent store coordinator, and managed object contexts.
Q10. What are CRUD operations?
Answer: CRUD means Create, Read, Update, and Delete. These operations form the foundation of data management in applications.
Q11. How do you insert a record using SwiftData?
Answer: Create an instance of a model and pass it to modelContext.insert(). Save the context when an immediate save is required.
Q12. How do you delete a record using Core Data?
Answer: Call context.delete(object) and save the managed object context.
Q13. What is an NSPredicate?
Answer: NSPredicate defines conditions used to filter objects in Core Data fetch requests.
Q14. What is data migration?
Answer: Data migration is the process of adapting existing persisted data when an application's data model changes.
Q15. When should you choose SwiftData?
Answer: SwiftData is a suitable choice for modern SwiftUI applications targeting iOS 17 or later when its features meet the application's persistence requirements.
23. Quick Revision Notes
- Core Data: Apple's established object graph and persistence framework.
- SwiftData: Modern Swift-native persistence framework.
- @Model: Makes a class persistable in SwiftData.
- ModelContainer: Manages the SwiftData schema and storage.
- ModelContext: Manages SwiftData model operations.
- @Query: Fetches and observes model data in SwiftUI.
- NSPersistentContainer: Sets up the Core Data stack.
- NSManagedObjectContext: Tracks and manages Core Data objects.
- NSFetchRequest: Describes a Core Data fetch operation.
- CRUD: Create, Read, Update, and Delete.
- Migration: Updates persisted data when the model schema changes.
- Persistence: Keeps application data available across launches.
24. Conclusion
Building a Notes App with Core Data or SwiftData provides practical experience with local data persistence in iOS development. The project demonstrates how to design data models, store information, fetch records, update existing data, and delete unwanted records.
Core Data remains an important framework for established applications and projects with broader deployment requirements. SwiftData offers a modern approach for SwiftUI applications through model macros, model containers, and model contexts.
By completing this project, developers gain a strong understanding of persistence, CRUD operations, data validation, searching, sorting, and model management. These concepts can also be applied to task managers, expense trackers, journaling applications, student management systems, and other data-driven iOS applications.
25. Additional Learning Resources
Continue Learning: Explore iOS development, Swift programming, SwiftUI, Core Data, SwiftData, and practical application development through JustAcademy's iOS Training Course.
Register for a Demo: Register for a Free Course Demo.
Visit: JustAcademy Official Website.