Swiftui Patterns
Builds and reviews SwiftUI views with modern MV architecture, state, composition, isolated previews, and migration guidance. Covers @Observable ownership, @State/@Bindable/@Environment wiring, view decomposition, ViewModifiers, environment values, .task loading, iOS 26+ handoffs, Writing Tools, clipboard availability, and performance. Use when structuring SwiftUI state, managing @Observable, composing views, previewing meaningful UI states, or correcting SwiftUI patterns.
- Skill ID
- dpearson2699/swift-ios-skills/swiftui-patterns
- Publisher
- dpearson2699
- Repository
- swift-ios-skills
- Installs
- 0
- Files
- 8
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
dpearson2699/swift-ios-skills/swiftui-patternsInstalls these files- SKILL.md
- evals/evals.json
- references/architecture-patterns.md
- references/deprecated-migration.md
- references/design-polish.md
- references/platform-and-sharing.md
- references/preview-isolation.md
- references/view-refactoring.md
What this skill tells the agent
SwiftUI Patterns
Modern SwiftUI patterns targeting iOS 26+ with Swift 6.3. Covers architecture, state management, view composition, environment wiring, async loading, design polish, and platform/share integration. Navigation, layout, animation, and Liquid Glass patterns live in dedicated sibling skills. Patterns are backward-compatible to iOS 17 unless noted.
Contents
- Architecture: Model-View (MV) Pattern
- Workflow
- State Management
- View Ordering Convention
- View Composition
- Environment
- Async Data Loading
- iOS 26+ New APIs
- Performance Guidelines
- HIG Alignment
- Writing Tools (iOS 18+)
- Common Mistakes
- Review Checklist
- References
Scope boundary: This skill covers architecture, state ownership, composition, environment wiring, async loading, and related SwiftUI app structure patterns. Detailed navigation patterns are covered in the swiftui-navigation skill, including NavigationStack, NavigationSplitView, sheets, tabs, and deep-linking patterns. Detailed layout, container, and component patterns are covered in the swiftui-layout-components skill, including stacks, grids, lists, scroll view patterns, forms, controls, search UI with .searchable, overlays, and related layout components. Detailed animation choreography is covered in swiftui-animation. Liquid Glass adoption, custom glass controls, scroll edge effects, .scrollEdgeEffectStyle, and .backgroundExtensionEffect are covered in swiftui-liquid-glass.
Workflow
- Record the current state ownership, actions, side effects, navigation, and lifecycle behavior.
- Choose the smallest MV/state/composition change that preserves that contract.
- Build after each structural step; fix compiler and isolation errors before continuing.
- Render deterministic previews for loaded, loading, empty, and error states as applicable, including required environment dependencies.
- Exercise important interactions and side effects. If behavior changes, restore the fixture, fix the smallest boundary, and rerun the same build, preview, and interaction checks.
Load Behavior-Preserving View Refactoring for restructuring existing views and Isolated Preview Construction for fixture and dependency patterns.
Architecture: Model-View (MV) Pattern
Default to MV -- views are lightweight state expressions; models and services own business logic. Do not introduce view models unless the existing code already uses them.
Core principles:
- Favor
@State,@Environment,@Query,.task, and.onChangefor orchestration - Inject services and shared models via
@Environment; keep views small and composable - Split large views into smaller subviews rather than introducing a view model
- Test models, services, and business logic; keep views simple and declarative
struct FeedView: View {
@Environment(FeedClient.self) private var client
enum ViewState {
case loading, error(String), loaded([Post])
}
@State private var viewState: ViewState = .loading
var body: some View {
List {
switch viewState {
case .loading:
ProgressView()
case .error(let message):
ContentUnavailableView("Error", systemImage: "exclamationmark.triangle",
description: Text(message))
case .loaded(let posts):
ForEach(posts) { post in
PostRow(post: post)
}
}
}
.task { await loadFeed() }
.refreshable { await loadFeed() }
}
private func loadFeed() async {
do {
let posts = try await client.getFeed()
viewState = .loaded(posts)
} catch {
viewState = .error(error.localizedDescription)
}
}
}For MV pattern rationale, app wiring, and lightweight client examples, see references/architecture-patterns.md.
State Management
@Observable Ownership Rules
Important: Isolate UI-bound @Observable stores and view models on @MainActor when SwiftUI views own them, mutate them, or bind to their properties. Observation tracks changes; it does not make shared mutable state thread-safe. Domain models that do not touch UI state can use their own isolation strategy.
| Wrapper | When to Use |
|---|---|
@State | View owns the object or value. Creates and manages lifecycle. |
let | View receives an @Observable object. Read-only observation -- no wrapper needed. |
@Bindable | View receives an @Observable object and needs two-way bindings ($property). |
@Environment(Type.self) | Access shared @Observable object from environment. |
@State (value types) | View-local simple state: toggles, counters, text field values. Always private. |
@Binding | Two-way connection to parent's @State or @Bindable property. |
Ownership Pattern
// UI-bound @Observable store -- main-actor isolated
@MainActor
@Observable final class ItemStore {
var title = ""
var items: [Item] = []
}
// View that OWNS the model
struct ParentView: View {
@State private var viewModel = ItemStore()
var body: some View {
ChildView(store: viewModel)
.environment(viewModel)
}
}
// View that READS (no wrapper needed for @Observable)
struct ChildView: View {
let store: ItemStore
var body: some View { Text(store.title) }
}
// View that BINDS (needs two-way access)
struct EditView: View {
@Bindable var store: ItemStore
var body: some View {
TextField("Title", text: $store.title)
}
}
// View that reads from ENVIRONMENT
struct DeepView: View {
@Environment(ItemStore.self) private var store
var body: some View {
@Bindable var s = store
TextField("Title", text: $s.title)
}
}Granular tracking: SwiftUI only re-renders views that read properties that changed. If a view reads items but not isLoading, changing isLoading does not trigger a re-render. This is a major performance advantage over ObservableObject.
Legacy ObservableObject
Only use if supporting iOS 16 or earlier. @StateObject → @State, @ObservedObject → let, @EnvironmentObject → @Environment(Type.self).
View Ordering Convention
Order members top to bottom: 1) @Environment 2) let properties 3) @State / stored properties 4) computed var 5) init 6) body 7) view builders / helpers 8) async functions
View Composition
Extract Subviews
Break views into focused subviews. Each should have a single responsibility. When restructuring an existing view, load Behavior-Preserving View Refactoring for action/side-effect boundaries and build/preview proof.
var body: some View {
VStack {
HeaderSection(title: title, isPinned: isPinned)
DetailsSection(details: details)
ActionsSection(onSave: onSave, onCancel: onCancel)
}
}Computed View Properties
Keep computed some View properties for small, stateless fragments. Extract a section into a dedicated View type when it has any of these signals:
- meaningful branching or substantial layout
- its own state or async lifecycle
- narrower Observation dependencies than the parent
- a useful independent preview
- enough complexity to obscure the parent's data flow
More from dpearson2699
Use this skill in a project
Open any RiverX project, search the Skills panel for dpearson2699/swift-ios-skills/swiftui-patterns, and install it in one click.
