Bird
Raised Fist0
iOS Swiftmobile~20 mins

CRUD operations with SwiftData in iOS Swift - Mini App: Build & Ship

Choose your learning style10 modes available
Build: Notes List
A simple notes app screen where users can create, read, update, and delete notes stored using SwiftData.
Target UI
-------------------------
| Notes List            |
|-----------------------|
| + Add Note            |
|-----------------------|
| - Note 1              |
| - Note 2              |
| - Note 3              |
|                       |
-------------------------
Display a list of notes stored with SwiftData.
Add a button to create a new note with a title and content.
Tap a note to edit its title and content.
Swipe left on a note to delete it.
Persist all changes using SwiftData.
Starter Code
iOS Swift
import SwiftUI
import SwiftData

@Model
class Note {
  var id: UUID = UUID()
  var title: String
  var content: String

  init(title: String = "", content: String = "") {
    self.title = title
    self.content = content
  }
}

struct NotesListView: View {
  @Query var notes: [Note]
  @Environment(\.modelContext) private var modelContext

  var body: some View {
    NavigationView {
      List {
        // TODO: Show notes here
      }
      .navigationTitle("Notes List")
      .toolbar {
        // TODO: Add button to add new note
      }
    }
  }
}

struct NotesListView_Previews: PreviewProvider {
  static var previews: some View {
    NotesListView()
  }
}
Task 1
Task 2
Task 3
Task 4
Task 5
Solution
iOS Swift
import SwiftUI
import SwiftData

@Model
class Note {
  var id: UUID = UUID()
  var title: String
  var content: String

  init(title: String = "", content: String = "") {
    self.title = title
    self.content = content
  }
}

struct NotesListView: View {
  @Query var notes: [Note]
  @Environment(\.modelContext) private var modelContext
  @State private var showingAddNote = false

  var body: some View {
    NavigationView {
      List {
        ForEach(notes, id: \.id) { note in
          NavigationLink(destination: NoteDetailView(note: note)) {
            Text(note.title.isEmpty ? "Untitled" : note.title)
          }
        }
        .onDelete(perform: deleteNotes)
      }
      .navigationTitle("Notes List")
      .toolbar {
        ToolbarItem(placement: .navigationBarTrailing) {
          Button(action: { showingAddNote = true }) {
            Image(systemName: "plus")
              .accessibilityLabel("Add Note")
          }
        }
      }
      .sheet(isPresented: $showingAddNote) {
        AddNoteView(isPresented: $showingAddNote)
          .environment(\.modelContext, modelContext)
      }
    }
  }

  private func deleteNotes(at offsets: IndexSet) {
    for index in offsets {
      let note = notes[index]
      modelContext.delete(note)
    }
  }
}

struct AddNoteView: View {
  @Environment(\.modelContext) private var modelContext
  @Binding var isPresented: Bool
  @State private var title = ""
  @State private var content = ""

  var body: some View {
    NavigationView {
      Form {
        TextField("Title", text: $title)
        TextEditor(text: $content)
          .frame(height: 150)
          .accessibilityLabel("Note Content")
      }
      .navigationTitle("New Note")
      .toolbar {
        ToolbarItem(placement: .cancellationAction) {
          Button("Cancel") {
            isPresented = false
          }
        }
        ToolbarItem(placement: .confirmationAction) {
          Button("Save") {
            let newNote = Note(title: title, content: content)
            modelContext.insert(newNote)
            isPresented = false
          }
          .disabled(title.trimmingCharacters(in: .whitespaces).isEmpty)
        }
      }
    }
  }
}

struct NoteDetailView: View {
  @ObservedObject var note: Note
  @Environment(\.modelContext) private var modelContext

  var body: some View {
    Form {
      TextField("Title", text: $note.title)
      TextEditor(text: $note.content)
        .frame(height: 150)
        .accessibilityLabel("Note Content")
    }
    .navigationTitle(note.title.isEmpty ? "Edit Note" : note.title)
    .toolbar {
      ToolbarItem(placement: .navigationBarTrailing) {
        Button("Save") {
          // Changes auto-saved by SwiftData
          modelContext.insert(note) // Ensure note is tracked
        }
      }
    }
  }
}

struct NotesListView_Previews: PreviewProvider {
  static var previews: some View {
    NotesListView()
  }
}

This app uses SwiftData to store notes with a title and content. The Note class is marked with @Model so SwiftData can manage it.

The NotesListView shows all notes using @Query. It has a plus button to add a new note, which opens a sheet with AddNoteView. This view lets the user enter a title and content, then saves the new note to the model context.

Tapping a note navigates to NoteDetailView, where the user can edit the note's title and content. Changes are automatically saved by SwiftData.

Users can swipe left on a note in the list to delete it, which removes it from the model context.

Accessibility labels are added for screen readers, and the UI uses standard SwiftUI navigation and forms for a familiar experience.

Final Result
Completed Screen
-------------------------
| Notes List            |
|-----------------------|
| + Add Note            |
|-----------------------|
| Note 1                |
| Note 2                |
| Note 3                |
|                       |
-------------------------

-- Tap '+' opens a form to add a new note.
-- Tap a note opens detail to edit.
-- Swipe left on a note to delete it.
Tap '+' button to open a modal form to add a new note with title and content.
Tap a note in the list to navigate to an edit screen where title and content can be changed.
Swipe left on a note row to reveal a delete action that removes the note.
All changes are saved automatically and reflected in the list.
Stretch Goal
Add dark mode support with automatic color scheme adaptation.
💡 Hint
Use SwiftUI's built-in color schemes and system colors to ensure the UI adapts automatically to light and dark modes.

Practice

(1/5)
1. What does the @Model attribute in SwiftData represent?
easy
A. A UI component for displaying lists
B. A data model class used to define entities for CRUD operations
C. A function to fetch data from the network
D. A SwiftUI view modifier for styling

Solution

  1. Step 1: Understand the role of @Model

    The @Model attribute marks a class as a data model entity for SwiftData, enabling CRUD operations.
  2. Step 2: Differentiate from UI and functions

    It is not related to UI components, network functions, or view modifiers.
  3. Final Answer:

    A data model class used to define entities for CRUD operations -> Option B
  4. Quick Check:

    @Model defines data entities = B [OK]
Hint: Remember @Model marks data classes, not UI or functions [OK]
Common Mistakes:
  • Confusing @Model with UI components
  • Thinking @Model fetches data
  • Mixing @Model with view modifiers
2. Which of the following is the correct way to declare a SwiftData model class named Book with a title property of type String?
easy
A. @Model class Book { var title: String }
B. @Model struct Book { var title: String }
C. struct Book { var title: String }
D. class Book: ObservableObject { var title: String }

Solution

  1. Step 1: Identify correct @Model usage

    SwiftData requires @Model on a class, not a struct, to define a data entity.
  2. Step 2: Check class vs struct and attribute

    @Model class Book { var title: String } correctly uses @Model with a class and a property.
  3. Final Answer:

    @Model class Book { var title: String } -> Option A
  4. Quick Check:

    @Model + class + properties = D [OK]
Hint: Use @Model with class, not struct, for SwiftData models [OK]
Common Mistakes:
  • Using struct instead of class for @Model
  • Omitting @Model attribute
  • Using ObservableObject instead of @Model
3. Given the following SwiftData code snippet, what will be the output when printing books.count after adding a new book?
struct ContentView: View {
  @Environment(\"modelContext\") private var modelContext
  @Query var books: [Book]

  var body: some View {
    Button(\"Add Book\") {
      let newBook = Book(title: \"SwiftData Basics\")
      modelContext.insert(newBook)
    }
    Text(\"Books count: \(books.count)\")
  }
}
medium
A. Books count: 0
B. Books count: 2
C. Books count: 1
D. Compilation error

Solution

  1. Step 1: Understand @Query and modelContext.insert

    The @Query property fetches all Book entities. Inserting a new Book updates the data source.
  2. Step 2: Check the effect of insertion on books.count

    After inserting one new Book, the count increases by 1, so it will be 1.
  3. Final Answer:

    Books count: 1 -> Option C
  4. Quick Check:

    Insert one book, count updates to 1 [OK]
Hint: Inserting updates @Query results immediately [OK]
Common Mistakes:
  • Assuming count stays 0 after insert
  • Expecting count to be 2 without adding twice
  • Thinking code causes compile error
4. Identify the error in this SwiftData update code snippet:
func updateBookTitle(book: Book, newTitle: String) {
  book.title = newTitle
  modelContext.save()
}
medium
A. No error; code updates title correctly
B. Missing call to modelContext.insert(book)
C. book.title cannot be changed directly; must create new Book
D. modelContext.save() is not a valid method in SwiftData

Solution

  1. Step 1: Check SwiftData update pattern

    In SwiftData, changes to model object properties are tracked automatically, but modelContext.save() is not a valid method to persist changes explicitly.
  2. Step 2: Identify if there's an error

    Directly mutating book.title is correct, but calling modelContext.save() causes an error because the method does not exist.
  3. Final Answer:

    modelContext.save() is not a valid method in SwiftData -> Option D
  4. Quick Check:

    Property change tracked automatically; no save() method = A [OK]
Hint: SwiftData tracks changes automatically; no save() method needed [OK]
Common Mistakes:
  • Thinking modelContext.save() exists
  • Believing properties are immutable
  • Confusing update with insert
5. You want to delete all Book objects with an empty title using SwiftData. Which code snippet correctly performs this operation?
hard
A. books.filter { $0.title.isEmpty }.forEach { modelContext.delete($0) }
B. for book in books where book.title.isEmpty { modelContext.delete(book) }
C. modelContext.deleteAll(Book.self, where: \"title == ''\")
D. modelContext.remove(books.filter { $0.title.isEmpty })

Solution

  1. Step 1: Understand deletion in SwiftData

    To delete objects, you call modelContext.delete(object) for each object.
  2. Step 2: Check each option for correctness

    books.filter { $0.title.isEmpty }.forEach { modelContext.delete($0) } filters books with empty titles and deletes each correctly. for book in books where book.title.isEmpty { modelContext.delete(book) } syntax is invalid in Swift. modelContext.deleteAll(Book.self, where: \"title == ''\") uses a non-existent method. modelContext.remove(books.filter { $0.title.isEmpty }) uses wrong method name.
  3. Final Answer:

    books.filter { $0.title.isEmpty }.forEach { modelContext.delete($0) } -> Option A
  4. Quick Check:

    Filter then delete each = A [OK]
Hint: Filter then delete each object individually [OK]
Common Mistakes:
  • Using invalid for-in with where clause
  • Calling non-existent deleteAll method
  • Using wrong method name like remove
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard