skills/swiftui-data-flow-expert/SKILL.md
SwiftUI data flow expert with @Observable, SwiftData, NavigationStack, and structured concurrency. Activate on: SwiftUI state management, @Observable, SwiftData, NavigationStack, Combine, async/await Swift, structured concurrency, MVVM SwiftUI. NOT for: UIKit legacy (use react-native-architect), Core Data migrations (use ios-core-data-architect), Android Compose (use jetpack-compose-navigation-expert).
npx skillsauth add curiositech/windags-skills swiftui-data-flow-expertInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert in SwiftUI state management with @Observable, SwiftData persistence, NavigationStack, and structured concurrency patterns for iOS 17+/18.
1. View-local state (form inputs, toggles, local UI)?
→ Use @State
→ Keep data inside the view
→ Example: @State private var showingSheet = false
2. Shared state across views (user session, app data)?
→ Use @Observable class
→ Pass via @Environment or init
→ Example: @Observable class UserSession
3. Persistent data (saved to disk)?
→ Use SwiftData @Model
→ Access via @Query in views
→ Example: @Model class Expense
4. External data source (network, sensors)?
→ Use @Observable + async methods
→ Wrap in Task {} for concurrency
→ Example: @Observable class WeatherViewModel
1. Single async operation (network call)?
→ Use Task {} in SwiftUI .task modifier
→ Handle errors with do/catch
2. Multiple related operations?
→ Use TaskGroup for parallel execution
→ Use await for sequential dependencies
3. Continuous data stream (location, sensors)?
→ Use AsyncStream or AsyncSequence
→ Cancel with .onDisappear
4. Background processing?
→ Use @globalActor or custom actor
→ Never access @MainActor data directly
1. Direct property binding needed?
→ Use @Bindable(viewModel)
→ Allows $ syntax for Observable objects
2. One-way data flow only?
→ Pass Observable directly
→ Read properties without $
3. Computed property or transformation?
→ Create computed var in Observable class
→ SwiftUI auto-tracks changes
4. Form data with validation?
→ Use @State for working copy
→ Sync to Observable on save/submit
Scenario: Build order list with SwiftData persistence, async loading, and type-safe navigation.
Step 1: Define the data model
@Model
class Order {
var id: UUID
var customerName: String
var total: Decimal
var status: OrderStatus
var createdAt: Date
init(customerName: String, total: Decimal) {
self.id = UUID()
self.customerName = customerName
self.total = total
self.status = .pending
self.createdAt = .now
}
}
Step 2: Navigation routing (expert catches type safety)
enum AppRoute: Hashable {
case orderDetail(Order.ID) // UUID, not Order object
case newOrder
}
Step 3: Observable view model for business logic
@Observable
class OrderStore {
var isLoading = false
var error: AppError?
@MainActor
func syncWithServer(context: ModelContext) async {
isLoading = true
defer { isLoading = false }
do {
let serverOrders = try await APIClient.shared.fetchOrders()
// Update SwiftData context
for serverOrder in serverOrders {
context.insert(serverOrder.toModelObject())
}
try context.save()
} catch {
self.error = .sync(error)
}
}
}
Step 4: SwiftUI view with decision points applied
struct OrderListView: View {
@Query(sort: \Order.createdAt, order: .reverse) private var orders: [Order]
@Environment(\.modelContext) private var modelContext
@State private var orderStore = OrderStore()
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List(orders) { order in
NavigationLink(value: AppRoute.orderDetail(order.id)) {
OrderRow(order: order)
}
}
.navigationTitle("Orders")
.toolbar {
Button("Add") { path.append(AppRoute.newOrder) }
Button("Sync", action: sync)
}
.navigationDestination(for: AppRoute.self) { route in
switch route {
case .orderDetail(let id):
OrderDetailView(orderId: id)
case .newOrder:
NewOrderView()
}
}
.overlay {
if orderStore.isLoading { ProgressView() }
}
.task { await sync() }
}
}
private func sync() async {
await orderStore.syncWithServer(context: modelContext)
}
}
Novice mistakes this catches:
Do NOT use this skill for:
ios-uikit-architect for legacy UI frameworksios-core-data-architect for existing Core Data stacksios-reactive-architect for existing Combine codebasesflutter-architect or react-native-architectios-legacy-swiftui for pre-@Observable patternsios-performance-engineer for Instruments analysisios-release-engineer for CI/CD and signingDelegate to other skills when:
native-app-designerapi-integration-specialistios-test-architecttools
Building resilient distributed systems with circuit breakers, retries with full-jitter exponential backoff, retry budgets (per-request 3-attempt + per-client 10% ratio per Google SRE), deadline propagation, and the cascading-failure math (4 layers × 3 retries = 64x amplification). Grounded in Resilience4j, Microsoft Cloud Patterns, AWS Architecture Blog (Marc Brooker), and Google SRE Book.
testing
Designing HTTP cache headers that work correctly across browsers, CDNs, and shared proxies — `Cache-Control` directives per RFC 9111, `stale-while-revalidate` and `stale-if-error` per RFC 5861, the Vary header for varying responses, and surrogate keys for tag-based purging. Grounded in IETF RFCs and Cloudflare/Fastly docs.
development
Use when designing or fixing a Content Security Policy on a real site, choosing between nonce-based and hash-based CSP, adding strict-dynamic, debugging "Refused to execute inline script" errors, deploying CSP in report-only mode first, configuring report-to / report-uri, or auditing an existing policy for unsafe-inline / unsafe-eval / wildcards. Triggers: "CSP blocks legitimate inline script", strict-dynamic, nonce-{RANDOM}, sha256-{HASH}, object-src none, base-uri none, frame-ancestors, Trusted Types, X-Content-Security-Policy obsolete, report-only vs enforced. NOT for general HTTP security headers (HSTS, COOP/COEP), Trusted Types deep dive, CORS configuration, or building a WAF.
tools
Choosing and operating an HTTP API versioning strategy that doesn't break clients — Stripe's date-based pinned versions, the Deprecation/Sunset header pair (RFC 9745 + RFC 8594), URI vs header vs media-type approaches, and the version-transformer pattern. Grounded in Stripe's published architecture and IETF RFCs.