iOS & SwiftUI Architecture Standards
Use this skill for iOS work built with SwiftUI, Swift Concurrency, and unidirectional state flow.
1. SwiftUI UI Rules
- Model-View-Intent (MVI) & UDF:
- Model: Expose single, immutable State properties from your ViewModel (or Reducer if using TCA).
- View: A pure function rendering the Model. State flows down.
- Intent: User actions are submitted to the ViewModel as explicit Intents/Events (e.g., via a
func send(_ event: Event)pattern). Events flow up.
- Screen vs View Approach:
<Name>Screenor<Name>Coordinator: Handles Environment injection, Navigation, and connects theViewModel(or Reducer) to the UI.<Name>View: A completely pure, stateless (or strictly internal view-state) SwiftUIViewthat accepts data via inputs and emits events via closures.
- Minimize Logic in Views:
- Do NOT put complex business validation or network requests in
.taskor.onAppearinside theViewstruct. - All business logic belongs in the injected
@ObservableViewModel or TCA Reducer.
- Do NOT put complex business validation or network requests in
- Swift 6 Concurrency in UI:
- SwiftUI
Viewstructs must be implicitly or explicitly@MainActorisolated. - Never use unstructured
Task { }without understanding the actor context; use.task { }modifiers tied to view lifecycle.
- SwiftUI
- Visibility & Previews:
- Keep helper Views
privateorfileprivateif they are only used within a single screen. - Provide
#Previewblocks for every distinct state of theView(loading, error, success).
- Keep helper Views
- Component Granularity:
- Minimize nesting. Extract deep view hierarchies into smaller computed properties returning
some Viewor separateViewstructs.
- Minimize nesting. Extract deep view hierarchies into smaller computed properties returning
2. State Management & Navigation
- Logic + State Separation:
- Public functions inside a ViewModel MUST represent user intents/events and generally return
Void. - Any function computing values internally should be
private. - State is exposed as published/observable properties.
- Public functions inside a ViewModel MUST represent user intents/events and generally return
- Observation:
- Use the
@Observablemacro exclusively. Do NOT use legacyObservableObject,@Published, orCombinebindings for State management unless interacting with legacy code.
- Use the
3. Dependency Injection
- Inject dependencies into ViewModels/UseCases via
init. - Use the SwiftUI
Environment(@Environment) ONLY for UI-specific dependencies (like formatters, themes, or global view routers), NOT for Repositories or UseCases.