Guidelines for building SwiftUI applications with MVVM/Clean Architecture, Swift 6 strict concurrency, and modern state management.
Use when building SwiftUI views, structuring view models, managing state with @State/@Observable, handling async data flow, or optimizing SwiftUI view performance.
Similar popular skills
Related neighbors and high-traction skills in the same topics — useful to compare before installing.
Files included with this skill beyond the listing page.
skill mdSKILL.md9,159 B
docsSUMMARY.md322 B
History
First seen on skills.sh
First recorded snapshot · 624 installs
SKILL.md
SwiftUI Development
This skill covers best practices for building clear, performant, and maintainable SwiftUI applications, including architecture, state management, Swift 6 concurrency, layout, animation, and testing.
Key Principles
Write correct, up-to-date, bug-free, fully functional, secure, and performant code
Favor readability, but not at the expense of correctness or safety
Fully implement all requested functionality — leave no TODOs, placeholders, or missing pieces
Target Swift 6.0+ with strict concurrency checking enabled; treat concurrency warnings as errors
Target iOS 17+ where possible to use modern APIs (@Observable, NavigationStack, #Preview); note the minimum deployment target when it forces older patterns
Architecture
Use MVVM (Model-View-ViewModel) or Clean Architecture; introduce a Coordinator layer for complex, multi-screen navigation flows
Apply SOLID principles to views and view models:
- Single Responsibility — each view or view model has one reason to change - Open/Closed — extend behavior via composition and protocols rather than modifying existing types - Liskov Substitution — protocol conformances must be substitutable without surprising callers - Interface Segregation — keep protocols small and focused rather than one large "do everything" interface - Dependency Inversion — depend on protocol abstractions, not concrete types, and inject dependencies via init
Implement protocol-oriented programming; prefer structs over classes for data models
Use extensions for code organization and separation of concerns
SwiftUI View Structure
Keep views small and focused on a single responsibility
Treat 50 lines as a practical ceiling for a body property — past that, extract subviews into small, reusable structs or private extension functions
Use @ViewBuilder for custom container views and complex conditional view logic
Implement proper view composition patterns
State Management
@State — local, value-type view state (Bool, Int, String, small structs)
@Binding — two-way data binding with child views
@StateObject — use only in the view that creates/owns the object's lifecycle
@ObservedObject — use in child views that react to changes but don't own the object
@EnvironmentObject / @Environment — use sparingly for broadly shared dependencies or system values; prefer explicit dependency injection via init when a dependency belongs to a specific view hierarchy rather than the whole app
@Published — for observable properties on ObservableObject classes (pre-iOS 17 or when ObservableObject is otherwise required)
@Observable macro (iOS 17+) — prefer this over ObservableObject/@Published for new view models; it removes the need for @Published on every property and only triggers view updates for properties actually read by that view, which reduces unnecessary redraws
Naming Conventions
camelCase for variables, functions, and methods; PascalCase for types (classes, structs, enums, protocols)
Use descriptive, verbose names — fetchUserData over getData
Prefix boolean variables with is, has, should, etc.
Use verb phrases for function names
SwiftUI Best Practices
Use SF Symbols for system icons; use semantic colors from the asset catalog for automatic dark mode support
Support Dynamic Type and implement proper keyboard avoidance
Use NavigationStack (iOS 16+) over the deprecated NavigationView
Prefer type-inferred shorthand dot syntax where available (e.g., .background(.blue))
Add .contentShape(Rectangle()) to HStack/VStack rows that have transparent backgrounds, so taps register across the whole row rather than only on opaque content
Layout and Styling
Use native SwiftUI layout containers (VStack, HStack, ZStack, Grid); use LazyVStack/LazyHStack for dynamic or long lists
Use GeometryReader sparingly — it expands to fill all available space and can hurt layout performance; prefer .background(GeometryReader { ... }) scoped to a single view, or the Layout protocol for custom layouts
Give list items stable, meaningful ids (from Identifiable) rather than \.self
Implement adaptive layouts for different screen sizes
Use ViewModifiers for reusable styling; create custom ButtonStyle, TextFieldStyle, etc.
Animations and Transitions
Prefer .animation(_:value:) scoped to a specific state value over broad withAnimation calls, especially inside body
Reserve withAnimation for animations explicitly triggered by user interaction
Implement custom transitions using AnyTransition; use matchedGeometryEffect for hero animations
Use TimelineView for high-frequency, time-driven visual updates instead of a Timer + published property
Swift 6 Concurrency & Data Flow
Annotate view models with @MainActor; all UI updates must happen on the main actor
Prefer .task(id:) over .onAppear for starting async work — it automatically cancels the task when the view disappears or the id changes, avoiding orphaned work
Use async/await for asynchronous operations and Result (or typed throws) for error handling
Prefer actor types for shared mutable state or services accessed from multiple tasks
Mark pure logic functions nonisolated when they don't touch the main actor, to avoid unnecessary hops
Never block the main thread — move heavy computation to a detached Task
Handle loading, error, and success states explicitly rather than leaving implicit/undefined states
Memory Management & Safety
Default to [weak self] in closures that outlive the current scope; use guard let self else { return } at the start of async closures
Only use [unowned self] when the closure's lifetime is provably shorter than self's
Handle optionals safely — no force unwrapping; use guard for early returns
For remote images, use AsyncImage (with a caching layer, or a library like Nuke/Kingfisher for production apps) and apply .resizable() immediately
Performance Optimization
Minimize view body recalculations; adopt Equatable conformance where it helps SwiftUI skip redundant diffing
Implement proper list diffing with Identifiable items and stable ids
Profile with Instruments before optimizing; cache expensive computations rather than recomputing them in body
Accessibility
Add accessibility labels, hints, and traits appropriately; support VoiceOver
Assign distinct accessibilityIdentifier strings to interactive elements so UI tests can target them reliably
Test with accessibility features (Dynamic Type, VoiceOver, Reduce Motion) enabled
Testing and Previews
Always provide a preview using #Preview (Xcode 15+) or PreviewProvider on older toolchains, injecting realistic mock data
Preview in multiple color schemes and device sizes
Structure unit tests with Given-When-Then; generate protocol-based mocks for external dependencies so view models can be tested without hitting real services
Code Quality
Write self-documenting code; add comments only for non-obvious logic
Follow the Swift API Design Guidelines
Use MARK: - Section Name to organize longer files, and place private helpers in a private extension
Common Patterns
View with @Observable ViewModel (iOS 17+)
@Observable
@MainActor
final class ContentViewModel {
var items: [Item] = []
var isLoading = false
func loadItems() async {
isLoading = true
defer { isLoading = false }
// Load items
}
}
struct ContentView: View {
@State private var viewModel = ContentViewModel()
var body: some View {
List(viewModel.items) { item in
Text(item.name)
}
.task(id: viewModel.items.count) {
await viewModel.loadItems()
}
}
}
View with ObservableObject ViewModel (pre-iOS 17 / legacy)
struct ContentView: View {
@StateObject private var viewModel = ContentViewModel()
var body: some View {
// View implementation
}
}
@MainActor
class ContentViewModel: ObservableObject {
@Published var items: [Item] = []
@Published var isLoading = false
func loadItems() async {
isLoading = true
// Load items
isLoading = false
}
}