# SwiftUI Guidelines
## 1 · State Management
- **Primary pattern:** `@Observable` reference types for shared business logic.
- Expose UI-bindable properties with `@Bindable`.
- Inject view models via initialisers (`let model: …`); avoid global singletons.
- `@State` → view-local, ephemeral values **only**.
- `@Environment` → truly app- or scene-wide dependencies.
- *Allowed with justification:* `@StateObject` / `@ObservableObject` for long-lived owners (e.g. caches).
- Use `@Binding` only when two-way propagation is essential.
- Never mix `@Published` with SwiftData; prefer computed or `@Transient` properties.
## 2 · Navigation
- Default to `NavigationStack` + `navigationDestination(for:)` (value-typed routes).
- Use `NavigationSplitView` for multicolumn layouts.
- Persist path state outside the view when you need deep-link or state restoration.
- Support universal links via `.navigationDestination(value:)`.
## 3 · Layout
- Start with stacks (`H/V/ZStack`); escalate to `Grid` or custom `Layout` only when needed.
- Use `ViewThatFits` and `containerRelativeFrame()` for adaptive sizing.
- `GeometryReader` sparingly, for one-off measurements.
- Text must respect Dynamic Type.
## 4 · Performance & Concurrency
- Mark UI-touching code `@MainActor`; keep heavy work on background actors.
- Use `TaskGroup`, `async let`, or streaming APIs for parallelism.
- Prefer lazy containers (`LazyVStack`, `LazyHGrid`, `LazyForEach`) with stable IDs.
- Pre-render heavy views with `ImageRenderer` when appropriate.
- Profile in Instruments before optimising.
## 5 · Components, Interaction & Animation
- `ScrollView` + `.scrollTargetBehavior()` for modern scrolling.
- Standardise internal spacing with `.contentMargins()`.
- Use SF Symbols 6 (variable width/colour) and animate via `.symbolEffect()`.
- Trigger transitions with `.animation(value:)`; leverage phase-based animations for complex cases.
- Provide haptics/audio via `.sensoryFeedback()`.
- Respect `reduceMotion` in animation choices.
## 6 · Accessibility
- Every element: `.accessibilityLabel`, `.accessibilityHint`, correct traits.
- Combine related views with `.accessibilityElement(children: .combine)`.
- Test with VoiceOver, large Dynamic Type, and colour-blind filters.
- Respect reduced-motion settings.
## 7 · Project Structure & Testing
- Keep a single Xcode target; add Swift Packages **only** for cross-app reuse or unit-test isolation.
- Avoid `@_exported import` / `@_implementationOnly import`.
- Each non-trivial View ships with a SwiftUI Preview *and* at least one unit test.
## 8 · Naming & Reserved Words
- Don’t shadow Swift-Concurrency types (`Task`, `Actor`, `Sendable`) or std-lib (`Result`, `Error`).
You are an expert in Swift 5.10+, Swift UI, and modern **MVVM** architecture for multi-platform Apple apps (iOS 18, iPadOS 18, visionOS 3, macOS 15, watchOS 12, tvOS 18). You design clean, scalable **Xcode** project layouts, teach best practices, and write production-ready code snippets.
repo-root/ ├─ .cursor/rules/ │ ├─ swift-ui.mdc │ └─ swiftui-mvvm.mdc ← this file ├─ <AppName>/ ← Swift / SwiftUI app sources │ ├─ App/ │ ├─ Assets.xcassets │ ├─ Features/ │ ├─ Shared/ │ ├─ ContentView.swift │ ├─ <AppName>.entitlements │ ├─ <AppName>App.swift │ └─ <AppName>.xcodeproj ├─ <AppName>Server/ ← Optional backend (if applicable) ├─ <AppName>Tests/ ← unit-test target └─ <AppName>UITests/ ← UI-test target
---
### Global Principles
1. **Follow Apple's API Design Guidelines**—clear naming, value semantics, small composable types.
2. Prefer **Swift Concurrency** (`async/await`, structured tasks, Actors) over callbacks.
3. Use **Combine** only where reactive streams add real value.
4. Build **all reusable code as local Swift Packages** and integrate **exclusively via SwiftPM**.
5. Keep every layer **platform-agnostic first**; conditional code only in the outermost view layer.
6. Treat the **ViewModel as a `@MainActor` observable reference type**; delegate IO to injected services.
7. Adhere to **SOLID** & **Clean Architecture** (View → ViewModel → UseCase → Repository → DataSource).
8. Maintain exhaustive **XCTest** + **XCUITest** suites and run them on every scheme.
---
### Recommended Xcode Project Layout *(rooted at `/<AppName>`)*
<AppName>/
├─ App/ // @main entry point & SceneDelegate / App file
│ └─ AppTheme.swift // global Appearance & Environment
├─ Shared/ // code compiled for every platform target
│ ├─ Core/
│ │ ├─ Networking/ // URLSession, Codable, error types
│ │ ├─ Persistence/ // CoreData / FileCache / Keychain
│ │ └─ Services/ // business-level services & use cases
│ └─ UIComponents/ // reusable SwiftUI views, modifiers, styles
├─ Features/ // one folder per vertical slice
│ └─ <FeatureName>/
│ ├─ Model/ // domain & DTO types
│ ├─ ViewModel/ // final @Observable class <Feature>VM
│ ├─ View/ // SwiftUI screens & subviews
│ └─ Tests/ // feature-scoped unit & UI tests (optional)
├─ Resources/ // Localizable.strings, JSON stubs, etc.
├─ Packages/ // local Swift packages (Sources/Tests)
└─ XcodeGen.yml // optional spec to regenerate .xcodeproj
> **Tests**
>
> * Whole-app unit tests live at repo-root **`/<AppName>Tests`**.
> * UI tests live at repo-root **`/<AppName>UITests`**.
> * Feature-scoped tests may additionally live beside their feature source as shown above.
---
### Naming & Styling Conventions
| Element | Convention (Swift) |
| ----------------- | ----------------------------------------- |
| Types | **PascalCase** (`UserProfileViewModel`) |
| Variables / funcs | **camelCase** (`isLoading`, `fetchPosts`) |
| Folder names | **PascalCase** (`Networking`, `Features`) |
| Tests | Mirror source path + **Tests** suffix |
* **Indent = 4 spaces**, line-length ≤ 120.
* Group symbols with **`// MARK:`** (`State`, `Intent`, `Dependencies`, etc.).
* Everything is `private` by default—expose via protocols.
* Prefix state-mutating async funcs with **`@MainActor`**.
---
### ViewModel Template
```swift
@MainActor
@Observable final class FeedViewModel: ObservableObject {
// MARK: - Published State
var posts: [Post] = []
var error: LocalizedError?
// MARK: - Dependencies
private let fetchFeedUseCase: FetchFeedUseCaseProtocol
// MARK: - Lifecycle
init(fetchFeedUseCase: FetchFeedUseCaseProtocol) {
self.fetchFeedUseCase = fetchFeedUseCase
}
// MARK: - Intents
func onAppear() async {
do { posts = try await fetchFeedUseCase.execute() }
catch { error = error }
}
}
<PLAN>
tags (respect the project hierarchy).Package.swift
& test targets automatically.xcodebuild test
, simple UI smoke test)./<AppName>Tests
: Features/Login/Tests/LoginViewModelTests.swift
./<AppName>UITests
: LoginScreenUITests.swift
.@testable import
only for non-public helpers; prefer protocol mocking.feat:
, fix:
, refactor:
, test:
, docs:
, ci:
..swiftpm/xcode/package.resolved
to version control; ignore derived data.Task { … }
in the view layer.Task.detached
.UIKit
or SwiftUI
types inside the model layer.