Develop new features in a modular iOS app using The Composable Architecture (TCA) and Swift Package Manager. Use when implementing a new screen, adding a sub-feature, extending an existing reducer, adding SwiftUI previews, or writing TCA tests. Covers the full development workflow: reading the existing codebase, creating reducer + view files, wiring navigation, writing SwiftUI previews for all meaningful states (loaded, empty, error, and feature-specific variants), and writing TestStore tests. Always explore the target module before writing code to match its existing style and conventions.
references/feature-template.mdreferences/view-patterns.mdreferences/testing-patterns.mdPackage.swift; wire into parent if it's a new tabBefore implementing any feature, read:
Sources/Services/ for available dependency clientsSources/Models/ for the relevant data types@Reducer macro on a struct@ObservableState on State, which conforms to Equatablepublic init() {} when all state fields have defaults; explicit memberwise init otherwise.onAppear: guard state.data == nil else { return .none }@Reducer enum Destination when the feature navigates to children (@Presents + ifLet)delegate(Delegate) action + @CasePathable enum Delegate for upward communicationcase .delegate: return .none — the reducer always ignores its own delegate cases.run: [client = someClient]String? via error.localizedDescription, not as Error type@Bindable var store: StoreOf<FeatureReducer> — always @Bindablepublic init(store:) — always explicit public initstore.send(.onAppear) in .onAppear modifierGroup { } for multi-branch body (loading / error / empty / content)ContentUnavailableView for error and empty states$store.scope(state:action:)Binding(get: { store.flag }, set: { _ in store.send(.toggleFlag) })@ViewBuilder private var computed propertiesEvery view must have at minimum 4 previews: loading (live reducer, fires .onAppear), content (pre-built state with sample data), empty, and any feature-specific variants (error, completed, failed, etc.).
NavigationStack { } when the view uses .navigationTitle or .navigationDestination"FeatureName - StateName" e.g. "Quiz - Partially Answered"withDependencies in previews — use the live reducer directlystatic var sample: Self / static var samples: [Self] on model types for preview dataSee references/feature-template.md for complete #Preview block templates (Patterns A–D).
| Question | Answer |
|---|---|
| Reducer + View in one file? | Yes, when combined ≤ ~200 lines |
| Where do computed properties go? | On State, not the view |
| How to pass data to a child feature? | Initialize child's State when setting destination |
| How to communicate upward? | .delegate(Delegate) action |
| Where does error text live? | state.loadError: String? via error.localizedDescription |
| How to cancel in-flight effects? | .cancellable(id:, cancelInFlight: true) |
| Where do sub-view components live? | File-private structs in the same .swift file |
Should I use UIViewRepresentable? | Only when SwiftUI has no equivalent (e.g. MKMapView, WKWebView). Never for styling convenience. |
| Which concurrency primitive? | async/await + AsyncStream first; Combine only if the API has no async alternative; GCD/NSLock only for legacy C/ObjC callbacks |
references/feature-template.md — Complete file templates: reducer, view with previews, tests, Package.swift snippet, AppCore wiring. Read when starting a new feature from scratch.references/view-patterns.md — View sub-patterns: loading/error/empty states, navigation wiring, Toggle binding, private sub-views, file-local components, animation. Read when implementing or refining a view.references/testing-patterns.md — TestStore setup, dependency mocking, async flow testing, delegate testing, computed property tests, idempotency tests. Read when writing or debugging tests.