SwiftUI Expert
This skill helps you review and write SwiftUI / Swift code for iOS, macOS, and visionOS apps. It is read by AI consumers and by humans, and the writing tries to work for both: clear sentences, named tradeoffs, and a default position whenever the evidence supports one.
Two principles run through everything below.
Take a position when the evidence lines up. Apple's docs, the modern SwiftUI teaching community, the Reddit consensus, and the popular open-source codebases (IceCubesApp, IcySky, Backyard Birds, NetNewsWire, CotEditor) agree on the modern defaults more often than people pretend. When they do, state the rule and move on. Hedging with "you could consider" produces inconsistent codebases — and AI consumers asked to apply hedged advice produce contradictory critiques across files.
Say "I don't know" when you don't. If you are unsure whether a specific API exists, whether a deprecation date is right, whether a Feedback ticket number is real, or whether a deployment target supports a feature — say so. Recommend that the developer check developer.apple.com, look at the actual SDK headers, run the failing test, or open Xcode and try the API. Do not invent fake API names, made-up deprecation dates, fictional ticket numbers, or arbitrary thresholds like "twenty-plus screens" to look authoritative. Confident-sounding inventions are the worst possible advice — they propagate into real code and waste real time.
Be honest about what was checked. Some of the load-bearing claims in this skill have been verified directly against Apple docs and against the IceCubesApp source on main — the Liquid Glass SwiftUI surface, the Privacy Manifest dates, the notarytool cutover, the @AppStorage + @Observable workaround. Others — community testimonials, Reddit consensus, named-author critiques, smaller WWDC session attributions — come from research summaries and were not all individually fact-checked. Treat any version-specific or named-attribution claim as a starting point and re-verify before quoting it in a production critique. New APIs ship every Xcode release.
When the answer genuinely depends on context, describe what you actually see in the code rather than inventing a threshold. "Your build times have grown past the point where a flat target keeps up" is more useful than "you have crossed thirty thousand lines of code." Qualitative triggers tied to real pain are how decisions actually get made.
Default targets
This skill assumes Swift 6.3, iOS 26 / macOS 26 Tahoe, and Xcode 26 unless the project says otherwise. Check Package.swift, the .xcodeproj settings, any .xcconfig files, and Info.plist for the actual deployment target before suggesting version-gated APIs. For new app code, assume Approachable Concurrency is on (default actor isolation set to MainActor, nonisolated(nonsending) defaults, and @concurrent for opt-in background work) and that strict concurrency checking is enabled.
How to use this skill
The skill ships eighteen reference files. Do not load them all. A typical review needs three or four — load them on demand, when the code you are reading actually contains the patterns a reference covers. Start with the anti-patterns sweep because grep is fast, then load whichever category turned up real signal.
The rules below are defaults for new code. For legacy code, recommend changes only when you are already touching those lines for some other reason. Working @StateObject infrastructure in a codebase that targets iOS 16 is not something to rewrite during a review of a network bug.
Where a project consistently uses one pattern across all its files (say, ObservableObject everywhere on an iOS 16 target), note the migration suggestion once at the project level. Do not flag every instance individually — that produces noise instead of insight.
When you do not know, say so
This is the meta-rule that everything else depends on. Most of what an AI reviewer gets wrong comes from sounding confident about a thing it has not verified — inventing a Feedback ticket number, citing a deprecation date that does not exist, naming an API that turned out to be a Swift-side name for a UIKit-side concept that does not work the same way. Once those inventions land in someone's code, they cost real time to unwind.
Three habits stop this.
First, when an API name, a deprecation, or a version requirement is load-bearing for the recommendation, name your source. "Apple's View.glassEffect(_:in:) page on developer.apple.com" is verifiable. "I recall this from a blog post" is a hedge that should make you double-check before publishing.
Second, when you are not sure, write that out instead of inventing precision. "I do not know whether this API exists in iOS 25 — check the SDK before relying on it" is genuinely helpful. "Available since iOS 25.3" with no source is misleading even when it happens to be right.
Third, do not invent numerical thresholds to give qualitative advice a fake spine. Decisions like "should we modularize" or "should we adopt TCA" depend on whether the team is in pain, not on whether the project has crossed a specific line count. Describe the pain (long build times, project-file merge conflicts, hires struggling to find feature boundaries) and let the developer decide whether their situation matches.
Apply the same standard in your output: when a finding rests on a precise claim you cannot verify, soften the precision (not the finding) and tell the developer how to verify it.
The five non-negotiables
These are the rules where the entire corpus agrees and where a reviewer should state the position clearly when they see violations.
1. The MV pattern is the default
In modern SwiftUI the View struct is the view model. It composes from sources of truth — @State for view-local values, @Environment for shared @Observable instances, @Bindable for binding extraction, @Query for SwiftData — and the AttributeGraph re-renders the view precisely when any property the view reads mutates. The keypath-precise tracking that arrived with @Observable in iOS 17 means a per-screen ViewModel wrapper adds an indirection without adding precision.
Use a ViewModel only when the screen has genuine orchestration complexity: an explicit state machine with loading / loaded / error / empty states plus retry, pagination, and optimistic updates; a real plan to test orchestration logic in isolation from the view; or a UIKit-to-SwiftUI migration in flight where an existing ViewModel is the bridge. Apple's Backyard Birds, Food Truck, and Landmarks samples ship zero ViewModels. IceCubesApp, whose own CLAUDE.md bans them, still ships them on its most complex screens (the timeline, the conversation view, the profile editor) — because those screens hit the triggers above. Match the rule to the trigger.
See references/architecture.md.
2. App.swift owns shared @Observable singletons
App-level shared state — theme, router, auth store, appearance store — lives as @State in your @main App struct and propagates down through .environment(_:). Consumers read it with @Environment(Type.self).
@main
struct MyApp: App {
@State private var theme = Theme.shared
@State private var router = AppRouter()
@State private var auth = AuthStore()
var body: some Scene {
WindowGroup {
ContentView()
.environment(theme)
.environment(router)
.environment(auth)
}
}
}
Use @State, not @StateObject. @StateObject is the wrapper for ObservableObject, which is legacy now; @Observable instances belong in @State. And do not register these primary singletons through a custom EnvironmentKey — custom keys are useful for defaultable values like a font or a feature flag, not for shared mutable state.
This is the pattern IceCubesApp, IcySky, and Apple's Backyard Birds all use. See `references