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-architectdata-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.