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

Generating views with AI (and reading the code)

Build the EntryList and EntryEditor screens by prompting well, then verify the output against your spec.

Now you'll generate real screens. The skill is prompting with enough context that the output fits your app — same data model, same spec, same conventions — and then reading it well enough to trust it.

Generate the list screen

Prompt to try

iOS 17+, SwiftUI + SwiftData. I have this model: [paste Entry.swift]. Build `EntryListView`: a NavigationStack with a List of entries sorted by createdAt descending, using @Query. Each row shows the aiTitle if present else the first line of body, plus the date in .secondary. A toolbar "+" button presents an EntryEditor in a .sheet. Support swipe-to-delete. Public APIs only; return the full file.

SCTB again: stack (iOS 17+, SwiftUI + SwiftData), target (EntryListView), behavior (rows, sheet, swipe-to-delete), constraint (public APIs only).

A good result looks like this. Read the annotations — they map directly onto the five fundamentals from the last lesson:

EntryListView.swiftswift
import SwiftUI
import SwiftData

struct EntryListView: View {
    @Environment(\.modelContext) private var context
    @Query(sort: \Entry.createdAt, order: .reverse) private var entries: [Entry]
    @State private var showingEditor = false

    var body: some View {
        NavigationStack {
            List {
                ForEach(entries) { entry in
                    NavigationLink(value: entry) {
                        EntryRow(entry: entry)
                    }
                }
                .onDelete(perform: delete)
            }
            .navigationTitle("DayOne")
            .navigationDestination(for: Entry.self) { EntryDetailView(entry: $0) }
            .toolbar {
                Button { showingEditor = true } label: { Image(systemName: "plus") }
            }
            .sheet(isPresented: $showingEditor) {
                EntryEditorView()
            }
        }
    }

    private func delete(_ offsets: IndexSet) {
        for i in offsets { context.delete(entries[i]) }
    }
}

private struct EntryRow: View {
    let entry: Entry
    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(entry.aiTitle ?? entry.body.firstLine)
                .font(.headline)
                .lineLimit(1)
            Text(entry.createdAt, format: .dateTime.month().day().year())
                .font(.caption)
                .foregroundStyle(.secondary)
        }
    }
}
Spot the thing that won't compile

entry.body.firstLine isn't a real Swift API — the model invented a convenient-sounding helper. This is exactly the 10% you must catch. The fix is a tiny extension, which you'd ask for next. The file also references EntryEditorView and EntryDetailView, which don't exist yet — you'll build those in the next lesson and the lab, so those two "Cannot find in scope" errors are expected until then.

String+FirstLine.swiftswift
extension String {
    /// First line of the string, or a placeholder if empty.
    var firstLine: String {
        split(separator: "\n").first.map(String.init) ?? "Untitled"
    }
}
The reading habit that saves hours

When code won't compile, read the error's first line and the file/line it points to before re-prompting. Half the time the fix is a one-line helper like the above, and you'll paste the exact error to the model: "firstLine doesn't exist — add an extension on String."