swift-ui-architect
SKILL.md
SwiftUI Modular MVVM-C Architecture
Opinionated architecture enforcement for SwiftUI clinic-style apps. This skill aligns to the iOS 26 / Swift 6.2 clinic architecture: modular MVVM-C in local SPM packages, concrete coordinators and route shells in the App target, pure Domain protocols, and Data as the only I/O layer.
Mandated Architecture Stack
┌───────────────────────────────────────────────────────────────┐
│ App target: DependencyContainer, Coordinators, Route Shells │
├───────────────┬───────────────┬───────────────┬──────────────┤
│ Feature* SPM │ Feature* SPM │ Feature* SPM │ Feature* SPM │
│ View + VM │ View + VM │ View + VM │ View + VM │
├───────────────────────────────────────────────────────────────┤
│ Data SPM: repository impls, remote/local, retry, sync queue │
├───────────────────────────────────────────────────────────────┤
│ Domain SPM: models, repository protocols, coordinator protocols│
│ and ErrorRouting/AppError │
├───────────────────────────────────────────────────────────────┤
│ Shared SPMs: DesignSystem, SharedKit │
└───────────────────────────────────────────────────────────────┘
Dependency Rule: Feature modules import Domain + DesignSystem only. Features never import Data or other features. App target is the only convergence point.
Clinic Architecture Contract (iOS 26 / Swift 6.2)
All guidance in this skill assumes the clinic modular MVVM-C architecture:
- Feature modules import
Domain+DesignSystemonly (neverData, never sibling features) - App target is the convergence point and owns
DependencyContainer, concrete coordinators, and Route Shell wiring Domainstays pure Swift and defines models plus repository,*Coordinating,ErrorRouting, andAppErrorcontractsDataowns SwiftData/network/sync/retry/background I/O and implements Domain protocols- Read/write flow defaults to stale-while-revalidate reads and optimistic queued writes
- ViewModels call repository protocols directly (no default use-case/interactor layer)
When to Apply
Reference these guidelines when:
- Building or refactoring feature modules under local SPM packages
- Wiring coordinators, route shells, and dependency container factories
- Defining Domain protocols for repositories, coordinators, and error routing
- Enforcing Data-only ownership of networking, persistence, and sync
- Reviewing stale-while-revalidate reads and optimistic queued writes
Non-Negotiable Constraints (iOS 26 / Swift 6.2)
@Observablefor ViewModels/coordinators,ObservableObject/@Publishednever- No dedicated use-case/interactor layer: ViewModels call Domain repository protocols directly
- Coordinator protocols live in Domain; concrete coordinators own
NavigationPathin App target - Route shells live in App target and own
.navigationDestinationmapping AppError+ErrorRoutingdrive presentation policy; ViewModels do not hardcode global error UI- SwiftData / URLSession / retry / sync queue logic stays in Data package only
Rule Categories by Priority
| Priority | Category | Impact | Prefix | Rules |
|---|---|---|---|---|
| 1 | View Identity & Diffing | CRITICAL | diff- |
6 |
| 2 | State Architecture | CRITICAL | state- |
7 |
| 3 | View Composition | HIGH | view- |
6 |
| 4 | Navigation & Coordination | HIGH | nav- |
5 |
| 5 | Layer Architecture | HIGH | layer- |
6 |
| 6 | Dependency Injection | MEDIUM-HIGH | di- |
4 |
| 7 | List & Collection Performance | MEDIUM | list- |
4 |
| 8 | Async & Data Flow | MEDIUM | data- |
5 |
Quick Reference
1. View Identity & Diffing (CRITICAL)
diff-equatable-views- Apply @Equatable macro to every SwiftUI viewdiff-closure-skip- Use @SkipEquatable for closure/handler propertiesdiff-reference-types- Never store reference types without Equatable conformancediff-identity-stability- Use stable O(1) identifiers in ForEachdiff-avoid-anyview- Never use AnyView — use @ViewBuilder or genericsdiff-printchanges-debug- Use _printChanges() to diagnose unnecessary re-renders
2. State Architecture (CRITICAL)
state-observable-class- Use @Observable classes for all ViewModelsstate-ownership- @State for owned data, plain property for injected datastate-single-source- One source of truth per piece of statestate-scoped-observation- Leverage @Observable property-level trackingstate-binding-minimal- Pass @Binding only for two-way data flowstate-environment-global- Use @Environment for app-wide shared dependenciesstate-no-published- Never use @Published or ObservableObject
3. View Composition (HIGH)
view-body-complexity- Maximum 10 nodes in view bodyview-extract-subviews- Extract computed properties/helpers into separate View structsview-no-logic-in-body- Zero business logic in bodyview-minimal-dependencies- Pass only needed properties, not entire modelsview-viewbuilder-composition- Use @ViewBuilder for conditional compositionview-no-init-sideeffects- Never perform work in View init
4. Navigation & Coordination (HIGH)
nav-coordinator-pattern- Every feature has a coordinator owning NavigationStacknav-routes-enum- Define all routes as a Hashable enumnav-deeplink-support- Coordinators must support URL-based deep linkingnav-modal-sheets- Present modals via coordinator, not inlinenav-no-navigationlink- Never use NavigationLink(destination:) — use navigationDestination(for:)
5. Layer Architecture (HIGH)
layer-dependency-rule- Domain layer has zero framework importslayer-usecase-protocol- Do not add a use-case layer; keep orchestration in ViewModel + repository protocolslayer-repository-protocol- Repository protocols in Domain, implementations in Datalayer-model-value-types- Domain models are structs, never classeslayer-no-view-repository- Views never access repositories directly; ViewModel calls repository protocolslayer-viewmodel-boundary- ViewModels expose display-ready state only
6. Dependency Injection (MEDIUM-HIGH)
di-environment-injection- Inject container-managed protocol dependencies via @Environmentdi-protocol-abstraction- All injected dependencies are protocol typesdi-container-composition- ComposeDependencyContainerin App target and expose VM factoriesdi-mock-testing- Every protocol dependency has a mock for testing
7. List & Collection Performance (MEDIUM)
list-constant-viewcount- ForEach must produce constant view count per elementlist-filter-in-model- Filter/sort in ViewModel, never inside ForEachlist-lazy-stacks- Use LazyVStack/LazyHStack for unbounded contentlist-id-keypath- Provide explicit id keyPath — never rely on implicit identity
8. Async & Data Flow (MEDIUM)
data-task-modifier- Use.task(id:)as the primary feature data-loading triggerdata-async-init- Never perform async work in initdata-error-loadable- Model loading states as enum, not booleansdata-combine-avoid- Prefer async/await over Combine for new codedata-cancellation- Use .task automatic cancellation — never manage Tasks manually
How to Use
Read individual reference files for detailed explanations and code examples:
- Section definitions - Category structure and impact levels
- Rule template - Template for adding new rules
Reference Files
| File | Description |
|---|---|
| references/_sections.md | Category definitions and ordering |
| assets/templates/_template.md | Template for new rules |
| metadata.json | Version and reference information |
Weekly Installs
31
Repository
pproenca/dot-skillsGitHub Stars
71
First Seen
Feb 15, 2026
Security Audits
Installed on
codex28
github-copilot27
gemini-cli27
opencode26
kimi-cli26
amp25