SwiftUI View Architect
Default to vanilla SwiftUI: local state in views, shared dependencies in environment/services, business logic outside view bodies, and view models only when requested or already present.
Ordering
Preserve stronger local conventions; otherwise order stored members top-to-bottom:
- environment
letinputs@State/other stored properties- non-view computed vars
initbody- view builders/helpers
- actions/async helpers
Rules
- Prefer MV over MVVM. Do not introduce a view model just to mirror local state or wrap environment dependencies.
- Split long bodies into dedicated
Viewtypes with explicit inputs/bindings/callbacks. Keep computedsome Viewhelpers small and rare. - Extract non-trivial button actions and side effects from
body; call private methods, and move real domain logic into services/models. - Keep a stable root view tree. Avoid top-level
if/elseswapping entire root branches; localize conditions in sections/modifiers/overlays/toolbars. - If a view model exists, prefer non-optional state initialized in
init:@State private var viewModel: SomeViewModel init(dependency: Dependency) { _viewModel = State(initialValue: SomeViewModel(dependency: dependency)) } - For
@Observableowners on iOS 17+, store as@Stateand pass explicitly. For iOS 16 or earlier, use@StateObjectowner and@ObservedObjectinjection. - Keep behavior/layout intact unless the user asked for a product change.
Workflow
- Reorder the view.
- Move inline actions/effects out of
body. - Extract meaningful sections into dedicated subviews; move reusable/independent subviews to files.
- Stabilize root structure.
- Normalize view model and Observation usage.
- Build/test the touched surface.
Large Views
For ~300+ line files, split aggressively into section views and small private helpers. // MARK: extensions can organize actions/helpers but are not a substitute for extracting real subviews.
Reference: references/mv-patterns.md. Use current Apple docs when SwiftUI/Observation behavior may have changed.