Back to course overview
Module 3Building the UI with SwiftUI + AI 12 min

State, bindings, and navigation

Build the editor screen, save into SwiftData, and understand the data flow that connects your screens.

The editor is where state and persistence meet. The user types into @State, and on Save you insert a new Entry into the SwiftData modelContext. Because the list uses @Query, it updates automatically — you never manually refresh it.

EntryEditorView.swiftswift
import SwiftUI
import SwiftData

struct EntryEditorView: View {
    @Environment(\.modelContext) private var context
    @Environment(\.dismiss) private var dismiss

    // Optional: when editing an existing entry we pass it in.
    var existing: Entry?
    @State private var text = ""

    var body: some View {
        NavigationStack {
            Form {
                TextEditor(text: $text)
                    .frame(minHeight: 220)
            }
            .navigationTitle(existing == nil ? "New Entry" : "Edit")
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel") { dismiss() }
                }
                ToolbarItem(placement: .confirmationAction) {
                    Button("Save", action: save)
                        .disabled(text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
                }
            }
            .onAppear { text = existing?.body ?? "" }
        }
    }

    private func save() {
        if let existing {
            existing.body = text
        } else {
            context.insert(Entry(body: text))
        }
        dismiss()
    }
}

Trace the data flow

  1. The user types → SwiftUI updates @State var text on every keystroke.
  2. The Save button's .disabled(...) re-evaluates as text changes, enabling itself once there's real content.
  3. On Save, you either mutate the existing Entry or context.insert a new one — SwiftData persists it.
  4. dismiss() closes the sheet. The list's @Query notices the new object and redraws. No manual refresh anywhere.
This is the whole pattern

Almost every screen in almost every app is: local @State for in-progress edits, a save that writes to persistent storage, and a query-backed list that reflects it. Once this clicks, you can read most SwiftUI apps.

Tip

Ask your assistant to "add a confirmation dialog before discarding an unsaved entry." It's a small, self-contained change — great practice at prompting for an incremental edit to existing code rather than regenerating the file.