SwiftUI Debugging
Diagnostic decision trees for SwiftUI view updates, preview crashes, and layout issues with 3 real-world examples.
When to use: View doesn't update, preview crashes, layout looks wrong, intermittent rendering issues
Key Features
- View Not Updating Decision Tree – 4-step diagnostic to identify struct mutation, binding identity loss, accidental view recreation, or missing observer pattern
- Preview Crashes Decision Tree – 3-branch diagnostic for missing dependencies, state initialization failures, or Xcode cache corruption
- Layout Issues Quick Reference – ZStack ordering, GeometryReader sizing, SafeArea, frame/fixedSize, modifier order
- Real-World Examples (3 complete workflows):
- List item doesn't update when tapped (struct mutation diagnosis)
- Preview crashes with "No Such Module" (missing dependency diagnosis)
- TextField value changes don't appear (binding identity diagnosis)
- Pressure Scenarios – Intermittent bugs under App Store Review deadline, co-lead authority pressure, professional push-back scripts
Requirements: Xcode 15+, iOS 14+
Example Prompts
These are real questions developers ask that this skill answers:
"List item doesn't update even though the data changed." → Demonstrates struct mutation diagnosis with SwiftUI update cycle
"Preview keeps crashing with mysterious dependency errors." → Covers preview crash decision tree: missing dependencies, state init failures, cache corruption
"TextField value changes don't appear on screen." → Shows binding identity diagnosis and when to use @State vs @Binding correctly
"Layout looks wrong. Elements are in the wrong positions." → Covers ZStack ordering, GeometryReader sizing, SafeArea, and modifier order issues
"I'm getting intermittent rendering glitches under deadline pressure." → Provides systematic troubleshooting under App Store Review stress with professional push-back scripts
Root Causes
Struct Mutation
You're modifying a @State value directly instead of reassigning it. SwiftUI can't see the change.
// ❌ WRONG
@State var items: [String] = []
items.append("new") // Direct mutation—SwiftUI doesn't see it
// ✅ RIGHT
@State var items: [String] = []
items.append("new")
self.items = items // Reassignment triggers updateLost Binding Identity
You're passing .constant() or creating a new binding each render, breaking the two-way connection.
// ❌ WRONG
TextField("Search", text: .constant(searchText))
// ✅ RIGHT
TextField("Search", text: $searchText)Accidental View Recreation
The view moved into a conditional or changed identity, losing its @State and resetting to defaults.
// ❌ WRONG
if showCounter {
Counter() // New identity each time condition changes
}
// ✅ RIGHT
Counter().opacity(showCounter ? 1 : 0) // Preserve identityMissing Observer
You're not using @StateObject or @ObservedObject, so SwiftUI doesn't watch for changes.
// ❌ WRONG
let model = MyModel()
// ✅ RIGHT
@StateObject var model = MyModel()Preview Crashes
Missing Dependencies
// ❌ WRONG
#Preview {
TaskDetailView(task: Task(...))
}
// ✅ RIGHT
#Preview {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try! ModelContainer(for: Task.self, configurations: config)
return TaskDetailView(task: Task(title: "Sample"))
.modelContainer(container)
}State Initialization Failure
Check array bounds, optional unwraps, and default values.
Cache Corruption
If preview worked yesterday and code hasn't changed:
- Restart Preview Canvas:
Cmd+Option+P - Restart Xcode
- Nuke derived data:
rm -rf ~/Library/Developer/Xcode/DerivedData
Related Skills
- xcode-debugging – For Xcode cache corruption and build issues
- swift-concurrency – For @MainActor and async/await patterns
- swiftui-performance – For performance optimization with SwiftUI Instrument
Quick Start
- Can you reproduce in a minimal preview? YES → code issue; NO → cache/environment
- Which root cause applies? Struct mutation → Lost binding → View recreation → Missing observer
- Apply the specific fix for that cause
- Verify in preview before shipping
Key principle: Start with observable symptoms, test systematically, eliminate causes one by one. Don't guess.
Learn more: See the full skill at /skills/ui-design/swiftui-debugging in Claude Code