Xcode 27 Agents: A Real SwiftUI Workflow From Plan To Preview To Tests
The healthiest way to use Xcode 27 agents is not to treat them like a magic autopilot.
Treat them like a fast, in-Xcode collaborator connected to the agent or model you enabled. They can read the project, look up Apple documentation, draft a plan, make edits, render previews, run builds and tests, and show you the diff. You still decide what the feature should be. You still review the code. You still run the app. You still keep or roll back the change.
That also means you should check Xcode’s intelligence settings and privacy behavior before you point an agent at a private codebase. Depending on the provider and configuration you choose, project context may be sent outside your Mac.
That distinction matters because the new workflow is genuinely useful. Apple’s WWDC26 “Xcode, agents, and you” session shows agents exploring an existing app, using Apple Document Search for framework details, entering plan mode with /plan, queueing follow-up requirements, producing artifacts, building, previewing, testing, and refining with inline annotations. “What’s new in Xcode 27” places that inside the redesigned editor, source control review mode, and Device Hub. The tools are stronger than “generate a file from a prompt.” The developer loop should get stronger too.
Let’s walk through that loop with a small SwiftUI feature: adding pinned notes to an existing notes app.
The feature is intentionally modest. A huge rewrite gives the agent too many ways to be clever. A small, useful feature gives you a better signal: can it understand the project, propose a sane implementation, make a focused diff, and verify the behavior?
The target feature
Imagine an existing app with a Note model, a note list, a note detail screen, and some tests around sorting. We want:
- A note can be pinned.
- The note list can switch between all notes and pinned notes.
- Pinned notes appear first in the all-notes list.
- The UI has a preview with sample pinned and unpinned notes.
- Tests cover the filter and sort behavior.
That is not glamorous, which is exactly why it is a good agent task. The work touches the model, one view model or query helper, one SwiftUI screen, previews, and tests. It is big enough to exercise the workflow, but small enough that a human can review the whole diff in one sitting.
Start by exploring, not editing
My first prompt would not ask Xcode to write code.
Summarize how notes flow through this app. Focus on the Note model, persistence,
the main note list, note row actions, previews, and existing tests. Include file
links and call out the smallest safe place to add a pinned-notes feature.
This is the part that saves the feature from being dropped into the wrong layer. In Apple’s session, agents can use Xcode project context such as source files, build settings, open files, and active selections. The transcript can become a project walkthrough with references to relevant files, and the generated notes can be preserved as artifacts if they are useful to the team.
Read the answer like you would read a teammate’s onboarding notes. Check that it found the real model type, the actual list view, the persistence mechanism, and the existing tests. If it says the app uses SwiftData but the code uses GRDB, stop and correct it before any edits happen.
For framework questions, ask for Apple Document Search directly:
Use Apple Document Search where helpful. I want this to stay idiomatic SwiftUI
and compatible with the app's current persistence layer. If a new API is beta-only
or changes deployment requirements, call that out before planning.
Apple described Document Search as a way for agents to access current Apple framework documentation instead of relying only on model knowledge. That is useful, but it is not a license to accept every suggestion. It is another input for your review.
Move into plan mode
Once the agent has the map, ask for a plan before code:
/plan
Add pinned notes to the existing notes app.
Requirements:
- Add an isPinned boolean to the note model using the app's existing persistence pattern.
- Add a segmented filter in the main list: All and Pinned.
- In All, sort pinned notes first, then by the app's existing note sort order.
- In Pinned, show only pinned notes and keep the existing sort order within that group.
- Add or update SwiftUI previews with at least one pinned note, one unpinned note,
and one long title.
- Add focused tests for sorting and filtering.
- Keep the change small. Do not redesign navigation, persistence, or the detail screen.
- Build and run the relevant tests when done.
This is where Xcode 27’s /plan flow is the most valuable. The plan gives you a chance to be the architect before edits exist. You can tighten scope, remove speculative files, and ask for alternatives while the cost is still low.
A good plan for this feature should name something like:
NotegainsisPinned.- Existing sample data gains mixed pinned states.
- A list filter enum is added near the list view or view model.
- Sorting is centralized in one helper so UI and tests agree.
- The list gets a small segmented
Picker. - Tests cover all-notes ordering and pinned-only filtering.
A concerning plan would mention a new database, a new navigation shell, a replacement list architecture, or broad visual redesign. That is when you edit the plan or send a queued follow-up:
Keep the existing persistence and note list architecture. Do not introduce a new
repository type unless the current tests already use one. Prefer a small sort/filter
helper that can be tested directly.
Apple showed queued messages as a way to add requirements while the agent is still working. Use that for the thoughts you would otherwise forget: “preserve empty state,” “do not change sync,” “avoid migration if this is only sample data,” “make the segmented control accessible.”
Review artifacts as they appear
In the Xcode agent UI, the transcript and artifacts are separate ideas. The transcript is the conversation and tool activity. Artifacts are the things produced: edits, files, diffs, previews, and similar outputs.
That separation changes the review habit. Do not wait until the agent is “done” and then skim the final summary. Click through the artifacts as they appear.
For this feature, I would expect to see a model change close to this shape:
import Foundation
struct Note: Identifiable, Equatable {
let id: UUID
var title: String
var body: String
var updatedAt: Date
var isPinned: Bool
}
If this is a SwiftData model, the exact code might be an @Model class instead of a struct. If this is Core Data, the model and generated accessors are different. The important review question is not “does it match this snippet?” It is “did the agent use the app’s existing data model correctly?”
The filter logic should be boring and testable:
enum NoteListFilter: String, CaseIterable, Identifiable {
case all
case pinned
var id: Self { self }
}
extension Array where Element == Note {
func filteredAndSorted(for listFilter: NoteListFilter) -> [Note] {
let visibleNotes = switch listFilter {
case .all:
self
case .pinned:
self.filter(\.isPinned)
}
return visibleNotes.sorted { lhs, rhs in
if lhs.isPinned != rhs.isPinned {
return lhs.isPinned && !rhs.isPinned
}
return lhs.updatedAt > rhs.updatedAt
}
}
}
That helper is deliberately plain. It gives the SwiftUI view one thing to call, and it gives tests a stable target. In a production app, you might push the sort into a database query for large collections. For a local list or in-memory view model, this is a fine starting shape.
The SwiftUI change should also stay small:
import SwiftUI
struct NotesListView: View {
@State private var filter: NoteListFilter = .all
let notes: [Note]
private var visibleNotes: [Note] {
notes.filteredAndSorted(for: filter)
}
var body: some View {
List(visibleNotes) { note in
NavigationLink {
NoteDetailView(note: note)
} label: {
NoteRow(note: note)
}
}
.navigationTitle("Notes")
.toolbar {
Picker("Filter", selection: $filter) {
Text("All").tag(NoteListFilter.all)
Text("Pinned").tag(NoteListFilter.pinned)
}
.pickerStyle(.segmented)
}
}
}
Again, your real app may use @Query, an observable store, a router, or a fetched results controller. The target shape is a focused filter control, not a new app architecture. This snippet shows only the display side; route pin and unpin actions through the app’s existing store, binding, row action, or detail action.
Use previews as a design checkpoint
The WWDC26 prototyping session is careful about this point: agents can help you create UI starting points, but you should not delegate critical thinking to them. Previews make that concrete because you can judge the result before arguing with code.
Ask for previews with real edge cases:
Add previews for NotesListView using sample data with:
- two pinned notes
- two unpinned notes
- one long title
- one empty pinned-filter state if the view supports it
Render the preview and report any layout or build issues.
The preview code might look like this:
#Preview("Pinned notes") {
NavigationStack {
NotesListView(notes: [
Note(
id: UUID(),
title: "Launch checklist",
body: "Verify screenshots, pricing, and release notes.",
updatedAt: .now,
isPinned: true
),
Note(
id: UUID(),
title: "A much longer note title that should wrap cleanly",
body: "Stress the row layout.",
updatedAt: .now.addingTimeInterval(-300),
isPinned: false
),
Note(
id: UUID(),
title: "Grocery list",
body: "Coffee, apples, rice.",
updatedAt: .now.addingTimeInterval(-600),
isPinned: true
)
])
}
}
Do not accept “the preview rendered” as the only design review. Look at the row spacing. Toggle the filter. Try long text. Check Dynamic Type if this is a real user-facing list. If the segmented control is too cramped in the toolbar, move it into a safe place in the content. If the pin affordance is invisible, ask for a targeted adjustment.
This is where inline annotations are useful. Instead of sending a vague prompt like “make the pinned row better,” annotate the row view or the exact line that draws the metadata:
Inline annotation on NoteRow's title HStack:
Show a pin icon only for pinned notes, but keep the row height stable and do not
hide the title from VoiceOver.
Inline annotations carry location. That makes the next change smaller because the agent has the surrounding code and the exact spot you care about.
Run the app in Device Hub when the screen matters
Previews are fast, but they are not the whole app. For a feature like pinned notes, run the screen in a simulator or physical device, especially if the app has navigation, search, keyboard focus, sync, or persisted state around the list.
Xcode 27’s Device Hub brings simulators and physical devices into one place. For this workflow, I would use it for the practical checks previews do not cover:
- Launch the app from a clean install.
- Pin and unpin a note if the feature includes an action.
- Switch between All and Pinned.
- Rotate if the app supports iPad or resizable iPhone windows.
- Try a larger text size.
- Confirm the empty state when no notes are pinned.
- Confirm the change survives relaunch if persistence was touched.
If the agent changed only the display filter, Device Hub is a quick confidence pass. If it changed persistence, this is where you slow down. Watch the migration path, existing user data, sync behavior, and any shared extension or widget assumptions.
Tests should prove behavior, not vibes
The agent should not stop at “it builds.” Ask for focused tests after the first implementation, or include them in the original plan.
Add tests for pinned note behavior. Cover:
- all notes returns pinned notes before unpinned notes
- pinned notes are still sorted by updatedAt within the pinned group
- pinned filter excludes unpinned notes
- empty pinned filter returns an empty collection
Run the relevant test target and fix failures caused by this change.
A useful test suite excerpt might look like this with Swift Testing:
import Foundation
import Testing
@testable import Notes
@Suite("Pinned note filtering")
struct PinnedNoteFilteringTests {
@Test
func allNotesShowsPinnedFirstThenRecentNotes() {
let olderPinned = note("Older pinned", pinned: true, minutesAgo: 20)
let newerPinned = note("Newer pinned", pinned: true, minutesAgo: 5)
let newestUnpinned = note("Newest unpinned", pinned: false, minutesAgo: 1)
let result = [newestUnpinned, olderPinned, newerPinned]
.filteredAndSorted(for: .all)
#expect(result.map(\.title) == [
"Newer pinned",
"Older pinned",
"Newest unpinned"
])
}
@Test
func pinnedFilterExcludesUnpinnedNotes() {
let pinned = note("Pinned", pinned: true, minutesAgo: 10)
let unpinned = note("Unpinned", pinned: false, minutesAgo: 1)
let result = [unpinned, pinned].filteredAndSorted(for: .pinned)
#expect(result == [pinned])
}
private func note(
_ title: String,
pinned: Bool,
minutesAgo: TimeInterval
) -> Note {
Note(
id: UUID(),
title: title,
body: "",
updatedAt: Date(timeIntervalSinceNow: -minutesAgo * 60),
isPinned: pinned
)
}
}
If your project still uses XCTest, keep XCTest. If the behavior lives in a database query, test the query against an in-memory store. If the app has UI tests around the note list, add one only if it buys real coverage. The right test is the one that catches the regression you are likely to ship.
Respect tool and permission boundaries
Xcode agents can use tools, and WWDC26 calls out several extension paths: built-in skills, external plug-ins that can provide skills, subagents, and MCP servers, and agents that support Agent Client Protocol. That is powerful, but it should make you more intentional, not less.
Set boundaries in the prompt:
Use Xcode build, preview, and test tools only. Do not run package updates,
network fetches, migration scripts, or formatting across unrelated files without
asking first. If a tool requires permission, explain why it is needed.
Then pay attention to permission prompts. A build or test run is usually expected. A package update, shell script, credentialed service call, or broad file rewrite deserves a pause. Agents are useful because they can act inside the development environment, but the human should still approve side effects with the same care they would use for a teammate’s script.
For a pinned-notes feature, I would scope the plan to:
- Read project files.
- Edit the specific model, list view, preview, and test files named in the plan.
- Build the app.
- Render the relevant preview.
- Run the relevant test target.
I would be cautious about:
- Updating dependencies.
- Reformatting the whole project.
- Running migrations against shared data.
- Touching sync, widgets, extensions, or CloudKit schema without a separate plan.
- Calling external services through MCP tools unless the task explicitly needs it.
Reserve grant and deny decisions for command-line tools, external tools, and MCP or plug-in permissions. Use prompt scope, plan review, artifacts, undo history, and source control review to keep file edits bounded. That caution is not anti-agent. It is the workflow that keeps agent work reviewable.
Review the diff like code you own
After the build, preview, and tests pass, switch from conversation mode to source control review mode and read the diff.
For this feature, check:
- Is
isPinnedadded in the right persistence layer? - Is any migration needed for existing users?
- Are default values correct for old notes?
- Does the sort order match the product requirement?
- Does the pinned filter work with search, sections, or archived notes?
- Did previews use realistic data and avoid leaking production fixtures?
- Did tests cover behavior without becoming brittle snapshots?
- Did the agent touch unrelated files?
- Are accessibility labels and VoiceOver output still sensible?
- Does the code look like the rest of the project?
If something is wrong, you have two good options.
For a small issue, annotate or prompt against the diff:
The filter helper currently sorts unpinned notes by title, but the existing app
sorts by updatedAt. Keep the existing updatedAt sort within each group and update
the tests to prove it.
For a broad wrong turn, roll back and re-plan. That is not failure. It is the reason source control review exists. A generated diff is a proposal until you accept it.
git diff -- src/Notes/Note.swift src/Notes/NotesListView.swift Tests/NotesTests
Read the patch. Keep the good parts only if they are easy to isolate. Otherwise revert the generated attempt and start from the improved prompt. The second plan is often much better because you now know where the first one wandered.
A working checklist
Here is the loop I would actually use on a real app:
- Ask Xcode to explore the project and summarize the relevant model, view, preview, and test files.
- Ask for Apple Document Search if the feature touches unfamiliar or beta APIs.
- Enter
/planand give tight requirements, non-goals, validation steps, and file boundaries. - Queue additional requirements while the plan is still forming.
- Review the plan and edit it before approving implementation.
- Watch artifacts as files, diffs, and previews appear.
- Build after the first implementation.
- Render SwiftUI previews with realistic edge cases.
- Run in Device Hub when app state, navigation, persistence, or device settings matter.
- Add focused tests and run the relevant target.
- Use inline annotations for precise UI or code refinements.
- Review the final source control diff.
- Keep, edit, or roll back the generated change.
The useful mental shift is that “agent work” is not one prompt. It is a loop: context, plan, edit, preview, test, diff, decision.
Beta caveats
This post is based on Apple’s WWDC26 Xcode 27 material available on June 22, 2026. Xcode 27 is still beta-era software, so exact UI labels, slash commands, permission prompts, plugin flows, and tool availability can change between seeds. Verify the current behavior in the Xcode seed installed on your machine.
Also remember that generated code still needs normal engineering review. Build success does not prove product fit. A passing preview does not prove accessibility. A passing unit test does not prove persistence migration. The agent can accelerate the loop, but you own the shipped app.
Official Apple sources:
- Xcode, agents, and you - WWDC26
- What’s new in Xcode 27 - WWDC26
- Create UI prototypes using agents in Xcode - WWDC26
- Get the most out of Device Hub - WWDC26
- 5 takeaways from the Platforms State of the Union
The part that should feel better
The best outcome is not that Xcode writes a pinned-notes feature while you watch passively. The best outcome is that you stay at the level where your judgment matters most.
You decide the product behavior. You constrain the architecture. You ask for the plan. You look at the preview. You run the device check. You read the tests. You review the diff. Xcode handles more of the mechanical searching, editing, and validation between those decisions.
That is a calmer way to use agents: fast, useful, and still human-checked from plan to preview to tests.