Comprehensive refactoring guide for migrating Swift/SwiftUI code to modular MVVM-C with local SPM package boundaries and App-target composition root wiring.
Feature modules never import
Data
and never import sibling features.
Clinic Architecture Contract (iOS 26 / Swift 6.2)
All guidance in this skill assumes the clinic modular MVVM-C architecture:
Feature modules import
Domain
+
DesignSystem
only (never
Data
, never sibling features)
App target is the convergence point and owns
DependencyContainer
, concrete coordinators, and Route Shell wiring
Domain
stays pure Swift and defines models plus repository,
*Coordinating
,
ErrorRouting
, and
AppError
contracts
Data
owns 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:
Migrating from deprecated SwiftUI APIs (ObservableObject, NavigationView, old onChange)
Restructuring state management to use @Observable ViewModels
Adding @Equatable diffing to views for performance
Decomposing large views into 10-node maximum bodies
Refactoring navigation to coordinator + route shell pattern
Refactoring to Domain/Data/Feature/App package boundaries
Setting up dependency injection through
DependencyContainer
Improving list/collection scroll performance
Replacing manual Task management with
.task(id:)
and cancellable loading
Non-Negotiable Constraints (iOS 26 / Swift 6.2)
@Observable
ViewModels/coordinators,
ObservableObject
/
@Published
never
NavigationStack
is owned by App-target route shells and coordinators
@Equatable
macro on every view,
AnyView
never
Domain defines repository/coordinator/error-routing protocols; no framework-coupled I/O
No dedicated use-case/interactor layer; ViewModels call repository protocols directly
Views never access repositories directly
Rule Categories by Priority
Priority
Category
Impact
Prefix
Rules
1
View Identity & Diffing
CRITICAL
diff-
4
2
API Modernization
CRITICAL
api-
7
3
State Architecture
CRITICAL
state-
6
4
View Composition
HIGH
view-
7
5
Navigation & Coordination
HIGH
nav-
5
6
Layer Architecture
HIGH
layer-
5
7
Architecture Patterns
HIGH
arch-
5
8
Dependency Injection
MEDIUM-HIGH
di-
2
9
Type Safety & Protocols
MEDIUM-HIGH
type-
4
10
List & Collection Performance
MEDIUM
list-
4
11
Async & Data Flow
MEDIUM
data-
3
12
Swift Language Fundamentals
MEDIUM
swift-
8
Quick Reference
1. View Identity & Diffing (CRITICAL)
diff-equatable-views
- Add @Equatable macro to every SwiftUI view
diff-closure-skip
- Use @EquatableIgnored for closure properties
diff-identity-stability
- Use stable O(1) identifiers in ForEach
diff-printchanges-debug
- Use _printChanges() to diagnose re-renders
2. API Modernization (CRITICAL)
api-observable-macro
- Migrate ObservableObject to @Observable macro
api-navigationstack-migration
- Replace NavigationView with NavigationStack
api-onchange-signature
- Migrate to new onChange signature
api-environment-object-removal
- Replace @EnvironmentObject with @Environment
api-alert-confirmation-dialog
- Migrate Alert to confirmationDialog API
api-list-foreach-identifiable
- Replace id: .self with Identifiable conformance
api-toolbar-migration
- Replace navigationBarItems with toolbar modifier
3. State Architecture (CRITICAL)
state-scope-minimization
- Minimize state scope to nearest consumer
state-derived-over-stored
- Use computed properties over redundant @State
state-binding-extraction
- Extract @Binding to isolate child re-renders
state-remove-observation
- Migrate @ObservedObject to @Observable tracking
state-onappear-to-task
- Replace onAppear closures with .task modifier
state-stateobject-placement
- Migrate @StateObject to @State with @Observable
4. View Composition (HIGH)
view-extract-subviews
- Extract subviews for diffing checkpoints
view-eliminate-anyview
- Replace AnyView with @ViewBuilder or generics
view-computed-to-struct
- Convert computed view properties to struct views
view-modifier-extraction
- Extract repeated modifiers into custom ViewModifiers
view-conditional-content
- Use Group or conditional modifiers over conditional views
view-preference-keys
- Replace callback closures with PreferenceKey
view-body-complexity
- Reduce view body to maximum 10 nodes
5. Navigation & Coordination (HIGH)
nav-centralize-destinations
- Refactor navigation to coordinator pattern
nav-value-based-links
- Replace NavigationLink with coordinator routes
nav-path-state-management
- Use NavigationPath for programmatic navigation
nav-split-view-adoption
- Use NavigationSplitView for multi-column layouts
nav-sheet-item-pattern
- Replace boolean sheet triggers with item binding
6. Layer Architecture (HIGH)
layer-dependency-rule
- Extract domain layer with zero framework imports
layer-usecase-protocol
- Remove use-case/interactor layer; keep orchestration in ViewModel + repository protocols
layer-repository-protocol
- Repository protocols in Domain, implementations in Data
layer-no-view-repository
- Remove direct repository access from views
layer-viewmodel-boundary
- Refactor ViewModels to expose display-ready state only
7. Architecture Patterns (HIGH)
arch-viewmodel-elimination
- Restructure inline state into @Observable ViewModel
arch-protocol-dependencies
- Extract protocol dependencies through ViewModel layer
arch-environment-key-injection
- Use Environment keys for service injection
arch-feature-module-extraction
- Extract features into independent modules
arch-model-view-separation
- Extract business logic into Domain models and repository-backed ViewModels
8. Dependency Injection (MEDIUM-HIGH)
di-container-composition
- Compose dependency container at app root
di-mock-testing
- Add mock implementation for every protocol dependency
9. Type Safety & Protocols (MEDIUM-HIGH)
type-tagged-identifiers
- Replace String IDs with tagged types
type-result-over-optionals
- Use Result type over optional with error flag
type-phantom-types
- Use phantom types for compile-time state machines
type-force-unwrap-elimination
- Eliminate force unwraps with safe alternatives
10. List & Collection Performance (MEDIUM)
list-constant-viewcount
- Ensure ForEach produces constant view count per element
list-filter-in-model
- Move filter/sort logic from ForEach into ViewModel
list-lazy-stacks
- Replace VStack/HStack with Lazy variants for unbounded content
list-id-keypath
- Provide explicit id keyPath — never rely on implicit identity
11. Async & Data Flow (MEDIUM)
data-task-modifier
- Replace onAppear async work with .task modifier
data-error-loadable
- Model loading states as enum instead of boolean flags
data-cancellation
- Use .task automatic cancellation — never manage Tasks manually
12. Swift Language Fundamentals (MEDIUM)
swift-let-vs-var
- Use let for constants, var for variables
swift-structs-vs-classes
- Prefer structs over classes
swift-camel-case-naming
- Use camelCase naming convention
swift-string-interpolation
- Use string interpolation for dynamic text
swift-functions-clear-names
- Name functions and parameters for clarity
swift-for-in-loops
- Use for-in loops for collections
swift-optionals
- Handle optionals safely with unwrapping
swift-closures
- Use closures for inline functions
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