
Swiftui Patterns
Apply Apple-aligned SwiftUI architecture (MV over MVVM) when building or refactoring iOS screens with your coding agent.
Overview
swiftui-patterns is an agent skill for the Build phase that teaches Model-View SwiftUI architecture, injection, and testing conventions for iOS UI work.
Install
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swiftui-patternsWhat is this skill?
- Defaults to Model-View (MV): views use @State, @Environment, @Query, .task, and .onChange
- Explicit guidance on when existing or new ViewModels are justified vs splitting subviews
- Environment vs initializer injection patterns for services and shared models
- Testing strategy focused on models and services while keeping views declarative
- Covers app wiring, dependency graph, and lightweight API clients
Adoption & trust: 2k installs on skills.sh; 713 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your agent keeps adding ViewModels and bloated views because there is no shared SwiftUI architecture ruleset aligned with Apple’s data-flow guidance.
Who is it for?
Solo builders shipping SwiftUI apps who want agent-written UI code to match Apple-style MV and environment-based services.
Skip if: UIKit-only codebases, teams mandating MVVM everywhere, or work that is pure CI/release with no SwiftUI structure decisions.
When should I use this skill?
Building or refactoring SwiftUI screens and you need MV-first architecture, injection, or testing conventions.
What do I get? / Deliverables
After applying the skill, new and refactored SwiftUI code follows MV defaults, justified ViewModel use, and testable service boundaries.
- Refactored SwiftUI structure following MV defaults
- Documented injection and testing approach for touched features
Recommended Skills
Journey fit
Canonical shelf is Build because the skill governs how you structure SwiftUI views, models, and services while implementing the product UI. Frontend subphase matches SwiftUI layout, state flow, and view decomposition—not backend or ship-time QA rituals.
How it compares
Use for opinionated SwiftUI structure guidance, not as an MCP server or a generic code-review checker.
Common Questions / FAQ
Who is swiftui-patterns for?
Indie and solo iOS developers using Claude Code, Cursor, or Codex who want consistent SwiftUI MV patterns while building screens and features.
When should I use swiftui-patterns?
During Build (frontend) when scaffolding SwiftUI flows, splitting large views, choosing injection, or deciding whether a ViewModel is warranted.
Is swiftui-patterns safe to install?
It is procedural documentation without bundled binaries; review the Security Audits panel on this Prism page before trusting the skill package source.
SKILL.md
READMESKILL.md - Swiftui Patterns
# Architecture Patterns ## Contents - [MV Patterns](#mv-patterns) - [App Wiring and Dependency Graph](#app-wiring-and-dependency-graph) - [Lightweight Clients](#lightweight-clients) ## MV Patterns Default to Model-View (MV) in SwiftUI. Views are lightweight state expressions; models and services own business logic. Do not introduce view models unless the existing code already requires them. ### Contents - [Core Principles](#core-principles) - [Why Not MVVM](#why-not-mvvm) - [MV Pattern in Practice](#mv-pattern-in-practice) - [When a ViewModel Already Exists](#when-a-viewmodel-already-exists) - [When a New ViewModel Is Justified](#when-a-new-viewmodel-is-justified) - [Environment vs. Initializer Injection](#environment-vs-initializer-injection) - [Testing Strategy](#testing-strategy) - [Source](#source) ### Core Principles - Views orchestrate UI flow using `@State`, `@Environment`, `@Query`, `.task`, and `.onChange` - Services and shared models live in the environment, are testable in isolation, and encapsulate complexity - Split large views into smaller subviews rather than introducing a view model - Test models, services, and business logic; views should stay simple and declarative ### Why Not MVVM SwiftUI views are structs -- lightweight, disposable, and recreated frequently. Adding a ViewModel means fighting the framework's core design. Apple's own WWDC sessions (*Data Flow Through SwiftUI*, *Data Essentials in SwiftUI*, *Discover Observation in SwiftUI*) barely mention ViewModels. Every ViewModel adds: - More complexity and objects to synchronize - More indirection and cognitive overhead - Manual data fetching that duplicates SwiftUI/SwiftData mechanisms ### MV Pattern in Practice #### View with Environment-Injected Service ```swift struct FeedView: View { @Environment(FeedClient.self) private var client @Environment(AppTheme.self) private var theme enum ViewState { case loading, error(String), loaded([Post]) } @State private var viewState: ViewState = .loading @State private var isRefreshing = false var body: some View { NavigationStack { List { switch viewState { case .loading: ProgressView("Loading feed...") .frame(maxWidth: .infinity) .listRowSeparator(.hidden) case .error(let message): ContentUnavailableView("Error", systemImage: "exclamationmark.triangle", description: Text(message)) .listRowSeparator(.hidden) case .loaded(let posts): ForEach(posts) { post in PostRowView(post: post) } } } .listStyle(.plain) .refreshable { await loadFeed() } .task { await loadFeed() } } } private func loadFeed() async { do { let posts = try await client.getFeed() viewState = .loaded(posts) } catch { viewState = .error(error.localizedDescription) } } } ``` #### Using .task(id:) and .onChange SwiftUI modifiers act as small state reducers: ```swift .task(id: searchText) { guard !searchText.isEmpty else { return } await searchFeed(query: searchText) } .onChange(of: isInSearch, initial: false) { guard !isInSearch else { return } Task { await fetchSuggestedFeed() } } ``` #### App-Level Environment Setup ```swift @main struct MyApp: App { @State var client = APIClient() @State var auth = Auth() @State var router = AppRouter(initialTab: .feed) var body: some Scene { WindowGroup { ContentView() .environment(client) .environment(auth) .environment(router) } } } ``` All dependencies are injected once and available everywhere. #### SwiftData: