Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
156: Building a Local-First Notes App with SwiftData
How do I define a Note model that SwiftData actually understands?
The magic happens with the @Model macro. In the old days of Core Data, you had to wrestle with a separate model editor file. Now, you just write a standard Swift class. I recommend keeping your properties simple—Strings and Dates are your best friends here.
import Foundation
import SwiftData
@Model
class Note {
var title: String
var content: String
var timestamp: Date
init(title: String = "", content: String = "", timestamp: Date = .now) {
self.title = title
self.content = content
self.timestamp = timestamp
}
}
Once you've added @Model, SwiftData takes care of the persistence schema. But remember, for this to actually work in your app, you have to tell SwiftUI about it at the entry point. You'll use the .modelContainer(for: Note.self) modifier on your WindowGroup. If you forget this, your app will crash the second you try to save a note because there's no "bucket" to put the data in.
How do I get my list of notes to update automatically in the UI?
This is where @Query comes in. Instead of writing a fetch request and manually updating an array, you just declare the query at the top of your view. It acts like a live connection to your database.
struct NoteListView: View {
@Query(sort: \Note.timestamp, order: .reverse)
private var notes: [Note]
var body: some View {
List(notes) { note in
VStack(alignment: .leading) {
Text(note.title).font(.headline)
Text(note.content).lineLimit(1).font(.subheadline)
}
}
}
}
I love this because it removes all the boilerplate. When you add a new note or delete one, the notes array updates automatically, and SwiftUI triggers a re-render. It feels almost like using a state variable, but the data is actually living on the disk.
What's the cleanest way to handle creating and deleting notes?
You need the modelContext. Think of the context as your "scratchpad." You make changes there first, and SwiftData handles the actual writing to the database. You can grab this context from the environment in any view.
struct NoteDetailView: View {
@Environment(\.modelContext) private var modelContext
@Bindable var note: Note
var body: some View {
Form {
TextField("Title", text: $note.title)
TextEditor(text: $note.content)
Button("Delete Note", role: .destructive) {
modelContext.delete(note)
}
}
}
}
One thing I've noticed is that beginners often wonder where the save() call is. In most SwiftUI setups, SwiftData autosaves. You don't usually need to call try modelContext.save() manually unless you're doing something complex or need to ensure the data is written before a specific background task begins.
Can I filter these notes based on a search term?
Yes, but @Query is a bit rigid because its parameters must be constant at compile-time. If you want a dynamic search bar, you can't just pass a variable into the @Query macro inside the same view. The pro move here is to wrap your list in a sub-view and pass the search text into that sub-view's initializer.
Inside that sub-view, you initialize the @Query using a #Predicate. It looks like this:
init(searchString: String) {
let predicate = #Predicate<Note> { note in
note.title.contains(searchString) || note.content.contains(searchString)
}
_notes = Query(filter: predicate, sort: \.timestamp, order: .reverse)
}
It's a slightly weird syntax (using the underscore _notes to access the Query wrapper itself), but it's the only way to make your local-first app feel responsive when the note count grows from ten to a thousand.
📋 Practical Task
Exercise: Implementing a "Pinned" Note Toggle
Currently, your notes are only sorted by date. Your task is to add a "Pinning" feature to the notes app.
- Modify the
Notemodel to include a Boolean property calledisPinned. - Update the
NoteListViewto show a pin icon next to each note. - Add a toggle (or button) in the
NoteDetailViewthat allows the user to flip theisPinnedstatus. - Challenge: Update the
@Queryin your list view so that pinned notes always appear at the top of the list, regardless of their timestamp. (Hint: You'll need to provide an array ofSortDescriptorobjects to the query).
There are no comments for now.