The SwiftUI AI Prompt That Enforces Swift 6, SOLID and Clean Architecture
Most AI-generated Swift code is confidently wrong. It uses completion handlers in 2025. It creates @Published properties in files that should never have heard of Combine. It wires URLSession directly into a ViewModel like it’s 2019. And the worst part? It looks correct at first glance.
That’s the trap.
After spending years building production iOS apps and reviewing way too many AI-generated pull requests, I got tired of fixing the same architectural mistakes over and over. So I stopped prompting AI like a beginner and started prompting it like a staff engineer.
This post shares the exact system prompt I use to get production-ready, Swift 6-compliant, SOLID-aligned SwiftUI code on the first try.
Why Most AI-Generated iOS Code Fails in Production
Here’s what typically happens. You ask an AI assistant to build a profile screen. It gives you a ViewModel that directly imports Foundation, hits a URLSession endpoint, publishes state via @Published, and uses ObservableObject. Clean? Looks like it. Shippable? Not even close.
The problems compound fast:
- No protocol abstraction means you can’t unit test anything
@Published+ObservableObjectis deprecated pattern territory in a Swift 6 world- Data races lurk everywhere because nobody slapped
@MainActoron the right classes CoreDatais still showing up when SwiftData has been available since iOS 17- Navigation is hard-coded with
NavigationLinkinstead of a properRouter
The issue isn’t the AI. The issue is the prompt. Garbage in, garbage out. But if you encode your architectural rules into the system prompt itself, the AI becomes a surprisingly powerful code generation engine.
The Architecture Stack This Prompt Enforces
Before sharing the prompt, let me walk through the decisions it’s built on. These aren’t arbitrary preferences. Every constraint has a production reason behind it.
Swift 6 Strict Concurrency
Swift 6 is a paradigm shift. If you haven’t dealt with its strict concurrency warnings turning into errors, you haven’t shipped a new project recently. The prompt enforces:
async/awaitonly. Completion handlers are banned.- All state-holding classes (
ViewModel,Store) are@MainActorisolated Sendableconformance where data crosses concurrency boundaries
This alone eliminates the most common class of production crash I’ve seen in the last two years.
Dependency Inversion (The D in SOLID)
This is non-negotiable. No View or ViewModel should ever directly touch a network layer, database, or third-party SDK. Everything goes through a Protocol.
I’ve written about this pattern in depth in my dependency injection guide for iOS. The short version: protocol-first design is what makes your code testable, mockable, and replaceable. Want to swap Supabase for a different backend? Change one concrete implementation. Everything else stays untouched.
The prompt forces this three-step structure every single time:
- Contract (Protocol):
protocol UserDataClient: Sendable { ... } - Implementation:
struct SupabaseUserClient: UserDataClient { ... } - Consumer (ViewModel):
@Observable @MainActor final class ProfileViewModel { init(client: UserDataClient) }
SwiftData Over CoreData
CoreData is powerful but it carries 20 years of API debt. SwiftData, introduced in iOS 17, gives you the same persistence engine with a dramatically cleaner API and native Swift macro support via @Model. The prompt bans CoreData entirely.
@Observable Over Combine
ObservableObject + @Published is not the future. @Observable (the macro, not the protocol) is leaner, faster, and doesn’t require AnyCancellable cleanup. The prompt enforces @Observable and bans Combine for state management.
Centralized Navigation with Router
Hard-coded NavigationLink destinations are a maintenance nightmare in any app with more than five screens. The prompt enforces NavigationStack + NavigationPath + a centralized Router class. This is the same pattern I cover in the iOS static framework architecture post when discussing module boundaries.
The Prompt (Copy-Paste Ready)
Here it is. Drop this into the system prompt field of your AI tool of choice (Cursor, Xcode AI, Claude, ChatGPT). It works best as a persistent system instruction rather than a one-off message.
Your Role: You are a Principal/Staff iOS Engineer. You write clean, scalable, thread-safe, and testable Swift code.
Tech Stack: iOS 17+, Swift 6 (Strict Concurrency), Pure SwiftUI, Swift Package Manager (SPM) Modules, @Observable macro, SwiftData.
ARCHITECTURE RULES (NON-NEGOTIABLE):
1. Modern Concurrency & Swift 6 Safety:
- Use async/await only. Completion handlers are FORBIDDEN.
- All state-holding classes (ViewModel/Store) MUST be isolated with @MainActor.
- Avoid any code that causes data races. Apply Sendable protocol where required.
2. Dependency Inversion (SOLID - D) is Mandatory:
- No View or ViewModel may directly perform networking (Network, SwiftData) or external service calls.
- Every external dependency (Supabase, API, etc.) MUST be hidden behind a Protocol (Interface).
- Real implementations are provided via dependency injection (DI) through the initializer.
3. Data Layer (Persistence):
- CoreData is FORBIDDEN. Use only iOS 17 SwiftData (@Model) for local persistence.
4. State and Navigation:
- Combine framework (ObservableObject, @Published) is BANNED. Use only the iOS 17 @Observable macro.
- Do not hard-code navigation with NavigationLink. Use NavigationStack, NavigationPath, and a centralized Router class.
- Avoid UIKit unless strictly required for an edge case.
5. Code Output Format:
Always provide the following three components in order:
a. Contract (Protocol): protocol UserDataClient: Sendable { ... }
b. Real Implementation: struct SupabaseUserClient: UserDataClient { ... }
c. Consumer (ViewModel/View): @Observable @MainActor final class ProfileViewModel { init(client: UserDataClient) }How to Use This in Real Projects
A few tips from actual usage:
Pair it with a feature description, not just a request. Instead of “build a login screen,” say “build a login screen that authenticates via Supabase, stores the session token using SwiftData, and navigates to a dashboard via the Router.” The more context you give on top of the system rules, the tighter the output.
Use SPM module boundaries as a forcing function. When your NetworkModule, DataModule, and UIModule are separate SPM packages, the AI naturally respects protocol boundaries because it literally can’t import the wrong layer. I touched on this separation-of-concerns approach in the Swift architecture evolution post.
Review the protocol layer first, always. Before you even look at the implementation, read the generated protocol. If the protocol is clean and minimal, the implementation usually follows. If the protocol is leaking implementation details (e.g., it returns a Supabase-specific type), reject it and ask for a revision.
Keep a “corrections log.” Every time the AI slips up and generates something that violates a rule, add a clarifying line to the system prompt. Over time, this prompt becomes a living document of your team’s architectural standards.
What This Unlocks for Solo Developers
If you’re building solo or as a small team, this kind of prompt is a force multiplier. You get the equivalent of a senior engineer reviewing every line of generated code against a strict checklist. The AI won’t always be perfect, but it will almost never make the same category of mistake twice.
This directly ties into the solo dev AI tools stack philosophy I’ve written about before: the goal isn’t to replace engineering judgment. It’s to automate the enforcement of decisions you’ve already made.
The Bigger Picture
AI coding assistants are only as good as the constraints you give them. Without architectural guardrails, they’ll generate code that passes a demo and fails a code review. With this prompt, you get output that’s ready to be reviewed by a principal engineer on day one.
Swift 6 strict concurrency, dependency inversion, protocol-first design, SwiftData, @Observable, centralized routing. These aren’t just buzzwords. They’re the foundation of every iOS app that actually scales.
Copy the prompt. Adapt it to your stack. And stop fixing AI-generated spaghetti.
