Swift & SwiftUI Expert
Expert-level guidance for Swift and SwiftUI development with modern patterns, iOS 17+/macOS 14+ best practices, and production-ready code generation.
Core Principles
When writing Swift/SwiftUI code, ALWAYS follow these principles:
1. Modern State Management (iOS 17+)
Use @Observable macro instead of ObservableObject:
// MODERN (iOS 17+)
@Observable
class UserViewModel {
var name: String = ""
var isLoggedIn: Bool = false
}
// View owns the model
struct ContentView: View {
@State private var viewModel = UserViewModel()
var body: some View {
Text(viewModel.name)
}
}
// Child view (read-only) - NO wrapper needed
struct ProfileView: View {
var viewModel: UserViewModel // Just pass it
var body: some View {
Text(viewModel.name)
}
}
// Child view (needs binding)
struct EditView: View {
@Bindable var viewModel: UserViewModel
var body: some View {
TextField("Name", text: $viewModel.name)
}
}
2. Property Wrapper Decision Tree
| Use Case | Property Wrapper |
|---|---|
| Local view state (value types) | @State |
| Two-way binding to parent state | @Binding |
| Observable model (view owns) | @State + @Observable class |
| Observable model (read-only child) | No wrapper (just pass object) |
| Observable model (needs binding) | @Bindable |
| Shared app-wide state | @Environment |
| User defaults | @AppStorage |
| Scene storage | @SceneStorage |
| Focus state | @FocusState |
3. Architecture Patterns
For most apps: Modern MVVM with @Observable
For large apps with teams: TCA (Composable Architecture)
For simple views: No ViewModel needed
See references/architecture.md for detailed patterns.
Anti-Patterns to AVOID
State Management
// WRONG: @ObservedObject with initialization @ObservedObject var viewModel = ViewModel() // Will crash! // RIGHT: Use @StateObject or @State @StateObject private var viewModel = ViewModel() // Legacy @State private var viewModel = ViewModel() // Modern (iOS 17+)
// WRONG: @State with reference types @State var user: User // Where User is a class // RIGHT: @State only for value types @State var userName: String @State private var viewModel = ViewModel() // Only with @Observable
// WRONG: Clearing data on error
func refresh() async {
self.items = [] // Data disappears if error occurs!
do {
self.items = try await fetchItems()
} catch {
// Items now empty forever
}
}
// RIGHT: Preserve existing data
func refresh() async {
do {
self.items = try await fetchItems()
} catch {
// Keep existing items, show error toast
}
}
Performance
// WRONG: Heavy computation in body
var body: some View {
let sorted = items.sorted { ... }.filter { ... } // Runs every render!
List(sorted) { ... }
}
// RIGHT: Memoize or compute elsewhere
var sortedItems: [Item] {
items.sorted { ... }.filter { ... }
} // Or use @State for expensive computations
// WRONG: Using id modifier on lazy content
LazyVStack {
ForEach(items) { item in
ItemView(item: item)
.id(item.id) // Breaks lazy loading!
}
}
// RIGHT: Let ForEach handle identity
LazyVStack {
ForEach(items) { item in
ItemView(item: item) // ForEach uses Identifiable
}
}
Navigation
// WRONG: Using deprecated NavigationView
NavigationView {
List { ... }
}
// RIGHT: Use NavigationStack (iOS 16+)
NavigationStack {
List { ... }
}
Code Generation Guidelines
When generating Swift/SwiftUI code:
- •Target iOS 17+/macOS 14+ unless user specifies otherwise
- •Use
@Observableinstead ofObservableObject/@Published - •Use NavigationStack instead of NavigationView
- •Use
.taskinstead of.onAppearfor async work - •Use Swift concurrency (async/await) instead of Combine for new code
- •Make @State private always
- •Prefer value types (structs, enums) over classes
- •Use optionals correctly - never force unwrap without safety
- •Handle errors explicitly - use do-catch, not try?
- •Add accessibility - labels, hints, traits
Reference Documents
For detailed guidance, consult:
- •
references/state-management.md- Property wrappers, @Observable, data flow - •
references/architecture.md- MVVM, TCA, Clean Architecture patterns - •
references/navigation.md- NavigationStack, programmatic navigation, deep links - •
references/performance.md- Lazy views, List optimization, render cycles - •
references/concurrency.md- async/await, actors, MainActor, Task - •
references/testing.md- Unit tests, ViewInspector, UI tests - •
references/accessibility.md- VoiceOver, Dynamic Type, accessibility modifiers - •
references/swiftdata.md- @Model, @Query, persistence patterns - •
references/anti-patterns.md- Common mistakes and how to fix them
Examples
Basic View with State
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 20) {
Text("Count: \(count)")
.font(.largeTitle)
Button("Increment") {
count += 1
}
.buttonStyle(.borderedProminent)
}
.accessibilityElement(children: .combine)
.accessibilityLabel("Counter at \(count)")
.accessibilityHint("Double tap the button to increment")
}
}
ViewModel with @Observable
@Observable
class TaskListViewModel {
var tasks: [Task] = []
var isLoading = false
var errorMessage: String?
@MainActor
func loadTasks() async {
isLoading = true
defer { isLoading = false }
do {
tasks = try await TaskService.shared.fetchTasks()
} catch {
errorMessage = error.localizedDescription
}
}
}
struct TaskListView: View {
@State private var viewModel = TaskListViewModel()
var body: some View {
NavigationStack {
Group {
if viewModel.isLoading {
ProgressView()
} else {
List(viewModel.tasks) { task in
TaskRow(task: task)
}
}
}
.navigationTitle("Tasks")
}
.task {
await viewModel.loadTasks()
}
}
}
Navigation with Type-Safe Routes
enum Route: Hashable {
case detail(Item)
case settings
case profile(userId: String)
}
struct ContentView: View {
@State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List(items) { item in
Button(item.name) {
path.append(Route.detail(item))
}
}
.navigationDestination(for: Route.self) { route in
switch route {
case .detail(let item):
DetailView(item: item)
case .settings:
SettingsView()
case .profile(let userId):
ProfileView(userId: userId)
}
}
}
}
// Programmatic navigation
func navigateToSettings() {
path.append(Route.settings)
}
func popToRoot() {
path.removeLast(path.count)
}
}
Usage
This skill activates automatically when working with Swift/SwiftUI files or when the user mentions iOS/macOS/SwiftUI development.
Explicit invocation:
/swift-swiftui help me refactor this view to use @Observable /swift-swiftui create a settings screen with navigation /swift-swiftui fix state management in this component