debug:swiftui

SwiftUI Debugging Guide

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "debug:swiftui" with this command: npx skills add snakeo/claude-debug-and-refactor-skills-plugin/snakeo-claude-debug-and-refactor-skills-plugin-debug-swiftui

SwiftUI Debugging Guide

A comprehensive guide for systematically debugging SwiftUI applications, covering common error patterns, debugging tools, and step-by-step resolution strategies.

Common Error Patterns

  1. View Not Updating

Symptoms:

  • UI doesn't reflect state changes

  • Data updates but view remains stale

  • Animations don't trigger

Root Causes:

  • Missing @Published on ObservableObject properties

  • Using wrong property wrapper (@State vs @Binding vs @ObservedObject)

  • Mutating state on background thread

  • Object reference not triggering SwiftUI's change detection

Solutions:

// Ensure @Published is used for observable properties class ViewModel: ObservableObject { @Published var items: [Item] = [] // Correct var count: Int = 0 // Won't trigger updates }

// Force view refresh with id modifier List(items) { item in ItemRow(item: item) } .id(UUID()) // Forces complete rebuild

// Update state on main thread DispatchQueue.main.async { self.viewModel.items = newItems }

  1. @State/@Binding Issues

Symptoms:

  • Child view changes don't propagate to parent

  • State resets unexpectedly

  • Two-way binding doesn't work

Solutions:

// Parent view struct ParentView: View { @State private var isOn = false

var body: some View {
    ChildView(isOn: $isOn)  // Pass binding with $
}

}

// Child view struct ChildView: View { @Binding var isOn: Bool // Use @Binding, not @State

var body: some View {
    Toggle("Toggle", isOn: $isOn)
}

}

  1. NavigationStack Problems

Symptoms:

  • Navigation doesn't work

  • Back button missing

  • Destination view not appearing

  • Deprecated NavigationView warnings

Solutions:

// iOS 16+ use NavigationStack NavigationStack { List(items) { item in NavigationLink(value: item) { Text(item.name) } } .navigationDestination(for: Item.self) { item in DetailView(item: item) } }

// For programmatic navigation @State private var path = NavigationPath()

NavigationStack(path: $path) { // ... }

// Navigate programmatically path.append(item)

  1. Memory Leaks with Closures

Symptoms:

  • Memory usage grows over time

  • Deinit never called

  • Retain cycles in view models

Solutions:

// Use [weak self] in closures viewModel.fetchData { [weak self] result in guard let self = self else { return } self.handleResult(result) }

// For Combine subscriptions, store cancellables private var cancellables = Set<AnyCancellable>()

publisher .sink { [weak self] value in self?.handleValue(value) } .store(in: &cancellables)

  1. Preview Crashes

Symptoms:

  • Canvas shows "Preview crashed"

  • "Cannot preview in this file"

  • Slow or unresponsive previews

Solutions:

// Provide mock data for previews #Preview { ContentView() .environmentObject(MockViewModel()) }

// Use @available to exclude preview-incompatible code #if DEBUG struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() .previewDevice("iPhone 15 Pro") } } #endif

// Simplify preview environment #Preview { ContentView() .modelContainer(for: Item.self, inMemory: true) }

  1. Combine Publisher Issues

Symptoms:

  • Publisher never emits

  • Multiple subscriptions

  • Memory leaks

  • Values emitted on wrong thread

Solutions:

// Ensure receiving on main thread for UI updates publisher .receive(on: DispatchQueue.main) .sink { value in self.updateUI(value) } .store(in: &cancellables)

// Debug publisher chain publisher .print("DEBUG") // Prints all events .handleEvents( receiveSubscription: { _ in print("Subscribed") }, receiveOutput: { print("Output: ($0)") }, receiveCompletion: { print("Completed: ($0)") }, receiveCancel: { print("Cancelled") } ) .sink { _ in } .store(in: &cancellables)

  1. Compiler Type-Check Errors

Symptoms:

  • "The compiler is unable to type-check this expression in reasonable time"

  • Generic error messages on wrong line

  • Build times extremely slow

Solutions:

// Break complex views into smaller components // BAD: Complex inline logic var body: some View { VStack { if condition1 && condition2 || condition3 { // Lots of nested views... } } }

// GOOD: Extract to computed properties or subviews var body: some View { VStack { conditionalContent } }

@ViewBuilder private var conditionalContent: some View { if shouldShowContent { ContentSubview() } }

  1. Animation Issues

Symptoms:

  • Animations not playing

  • Jerky or stuttering animations

  • Wrong elements animating

Solutions:

// Use withAnimation for explicit control Button("Toggle") { withAnimation(.spring()) { isExpanded.toggle() } }

// Apply animation to specific value Rectangle() .frame(width: isExpanded ? 200 : 100) .animation(.easeInOut, value: isExpanded)

// Use transaction for fine-grained control var transaction = Transaction(animation: .easeInOut) transaction.disablesAnimations = false withTransaction(transaction) { isExpanded.toggle() }

Debugging Tools

Xcode Debugger

Breakpoints:

// Conditional breakpoint // Right-click breakpoint > Edit Breakpoint > Condition: items.count > 10

// Symbolic breakpoint for SwiftUI layout issues // Debug > Breakpoints > Create Symbolic Breakpoint // Symbol: UIViewAlertForUnsatisfiableConstraints

LLDB Commands:

Print view hierarchy

po view.value(forKey: "recursiveDescription")

Print SwiftUI view

po self

Examine memory

memory read --size 8 --format x 0x12345678

Find retain cycles

leaks --outputGraph=/tmp/leaks.memgraph [PID]

Instruments

Allocations:

  • Track memory usage over time

  • Identify objects not being deallocated

  • Find retain cycles

Time Profiler:

  • Identify slow code paths

  • Find main thread blocking

  • Optimize view rendering

SwiftUI Instruments (Xcode 15+):

  • View body evaluations

  • View identity tracking

  • State change tracking

Print Debugging

// Track view redraws var body: some View { let _ = Self._printChanges() // Prints what caused redraw Text("Hello") }

// Conditional debug printing #if DEBUG func debugPrint(_ items: Any...) { print(items) } #else func debugPrint(_ items: Any...) {} #endif

// os_log for structured logging import os.log

let logger = Logger(subsystem: "com.app.name", category: "networking") logger.debug("Request started: (url)") logger.error("Request failed: (error.localizedDescription)")

View Hierarchy Debugger

  • Run app in simulator/device

  • Click "Debug View Hierarchy" button in Xcode

  • Use 3D view to inspect layer structure

  • Check for overlapping views, incorrect frames

Environment Inspection

// Print all environment values struct DebugEnvironmentView: View { @Environment(.self) var environment

var body: some View {
    let _ = print(environment)
    Text("Debug")
}

}

The Four Phases (SwiftUI-Specific)

Phase 1: Reproduce and Isolate

Create minimal reproduction

  • Strip away unrelated code

  • Use fresh SwiftUI project if needed

  • Test in Preview vs Simulator vs Device

Identify trigger conditions

  • When does the bug occur?

  • What user actions trigger it?

  • Is it state-dependent?

Check iOS version specifics

  • Does it happen on all iOS versions?

  • Is it simulator-only or device-only?

Phase 2: Diagnose

Use Self._printChanges()

var body: some View { let _ = Self._printChanges() // Your view content }

Add strategic breakpoints

  • Body property

  • State mutations

  • Network callbacks

Check property wrapper usage

  • @State for view-local state

  • @Binding for parent-child communication

  • @StateObject for owned ObservableObject

  • @ObservedObject for passed ObservableObject

  • @EnvironmentObject for dependency injection

Verify threading

// Check if on main thread assert(Thread.isMainThread, "Must be on main thread")

Phase 3: Fix

Apply targeted fix

  • Fix one issue at a time

  • Don't introduce new property wrappers unnecessarily

Test the fix

  • Verify in Preview

  • Test in Simulator

  • Test on physical device

  • Test edge cases

Check for side effects

  • Run existing tests

  • Verify related features still work

Phase 4: Prevent

Add unit tests

func testViewModelUpdatesState() async { let viewModel = ViewModel() await viewModel.fetchData() XCTAssertEqual(viewModel.items.count, 10) }

Add UI tests

func testNavigationFlow() { let app = XCUIApplication() app.launch() app.buttons["DetailButton"].tap() XCTAssertTrue(app.staticTexts["DetailView"].exists) }

Document the fix

  • Add code comments explaining why

  • Update team documentation

Quick Reference Commands

Xcode Shortcuts

Shortcut Action

Cmd + R Run

Cmd + B Build

Cmd + U Run tests

Cmd + Shift + K Clean build folder

Cmd + Option + P Resume preview

Cmd + 7 Show debug navigator

Cmd + 8 Show breakpoint navigator

Common Debug Snippets

// Force view identity reset .id(someValue)

// Track view lifecycle .onAppear { print("View appeared") } .onDisappear { print("View disappeared") } .task { print("Task started") }

// Debug layout .border(Color.red) // See frame boundaries .background(Color.blue.opacity(0.3))

// Debug geometry .background(GeometryReader { geo in Color.clear.onAppear { print("Size: (geo.size)") print("Frame: (geo.frame(in: .global))") } })

// Debug state changes .onChange(of: someState) { oldValue, newValue in print("State changed from (oldValue) to (newValue)") }

Build Settings for Debugging

// In scheme > Run > Arguments > Environment Variables OS_ACTIVITY_MODE = disable // Reduce console noise DYLD_PRINT_STATISTICS = 1 // Print launch time stats

Memory Debugging

// Add to class to track deallocation deinit { print("(Self.self) deinit") }

// Enable Zombie Objects // Edit Scheme > Run > Diagnostics > Zombie Objects

// Enable Address Sanitizer // Edit Scheme > Run > Diagnostics > Address Sanitizer

Resources

  • SwiftUI Debugging - SwiftLee

  • Building SwiftUI Debugging Utilities - Swift by Sundell

  • Common SwiftUI Errors - Hacking with Swift

  • 8 Common SwiftUI Mistakes - Hacking with Swift

  • Advanced Debugging Techniques - MoldStud

  • Debugging SwiftUI with Xcode - Kodeco

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

refactor:flutter

No summary provided by upstream source.

Repository SourceNeeds Review
General

refactor:nestjs

No summary provided by upstream source.

Repository SourceNeeds Review
General

debug:flutter

No summary provided by upstream source.

Repository SourceNeeds Review
General

refactor:spring-boot

No summary provided by upstream source.

Repository SourceNeeds Review