SwiftUI @State Updates Not Reflecting in UI Until View Reconstruction (Xcode Preview & Device)

Issue Description I'm experiencing a bizarre SwiftUI state update issue that only occurs in Xcode development environment (both Canvas preview and device debugging), but does not occur in production builds downloaded from App Store.

Symptom:

  • User taps a button that modifies a @State variable inside a .sheet

  • Console logs confirm the state HAS changed

  • But the UI does not update to reflect the new state

  • Switching to another file in Xcode and back to ContentView instantly fixes the issue

  • The production build (same code) works perfectly fine

Environment

  • Xcode: 16F6 (17C52)

  • iOS: 26.2 (testing on iPhone 13)

  • macOS: 25.1.0 (Sequoia)

  • SwiftUI Target: iOS 15.6+

  • Issue: Present in both Xcode Canvas and on-device debugging

  • Production: Same code works correctly in App Store build (version 1.3.2)

Code Structure

Parent View (ContentView.swift)

struct ContentView: View { @State private var selectedSound: SoundTheme = .none @State private var showSoundSheet = false var body: some View { VStack { // Display button shows current selection SettingButton( title: "Background Sound", value: getLocalizedSoundName(selectedSound) // ← Not updating ) { showSoundSheet = true } } .sheet(isPresented: $showSoundSheet) { soundSelectionView } } private var soundSelectionView: some View { ForEach(SoundTheme.allCases) { sound in Button { selectedSound = sound // ← State DOES change (confirmed in console) // Audio starts playing correctly audioManager.startAmbientSound(sound) } label: { Text(sound.name) } } } private func getLocalizedSoundName(_ sound: SoundTheme) -> String { // Returns localized name return sound.localizedName }}

What I've Tried

Attempt 1: Adding .id() modifier SettingButton(...) .id(selectedSound) // Force re-render when state changes

Result: No effect

Attempt 2: Moving state modification outside withAnimation // Before (had animation wrapper):withAnimation { selectedSound = sound}// After (removed animation):selectedSound = sound

Result: No effect

Attempt 3: Adding debug print() statements selectedSound = soundprint("State changed: (selectedSound)") // ← Adding this line FIXES the issue!

Result: Mysteriously fixes the issue! But removing print() breaks it again. This suggests a timing/synchronization issue in Xcode's preview system. Observations

What works: ✅ Console logs confirm state changes correctly ✅ Switching files in Xcode triggers view reconstruction → everything works ✅ Production build from App Store works perfectly ✅ Adding print() statements "fixes" it (likely changes execution timing)

What doesn't work: ❌ Initial file load in Xcode ❌ Hot reload / incremental updates ❌ Both Canvas preview and on-device debugging

Workaround that works:

  • Click another file in Xcode
  • Click back to ContentView.swift
  • Everything works normally

Key Question

Is this a known issue with Xcode 16's SwiftUI preview/hot reload system?

The fact that:

  • Same exact code works in production
  • Adding print() "fixes" it
  • File switching triggers reconstruction that fixes it

...all suggest this is an Xcode tooling issue, not a code bug.

However, it makes development extremely difficult as I can't reliably test changes without constantly switching files or killing the app.

What I'm Looking For

  • Confirmation: Is this a known Xcode 16 issue?
  • Workaround: Any better solution than constantly switching files?
  • Root cause: What's causing this state update timing issue?

Any insights would be greatly appreciated!

Can't do much with the code as provided.

Please reply with the code within the code block. Use the formatting tools to do this, or just type three backticks (`) in a row at the start and end of your code.

And please ensure all relevant code is present.

Example:

import SwiftUI

struct ContentView: View {
  var body: some View {
    ...
  }
}

Thanks for your reply! Here's the complete code and detailed information:

Issue Description

I'm experiencing a strange behavior with Xcode Preview in my SwiftUI app:

  1. On first Preview load: Button taps don't respond at all
  2. Temporary workaround: Click any other file, then click back to ContentView - buttons work perfectly
  3. Reproducibility: Happens 100% of the time after cleaning and restarting Preview

Environment

  • Xcode Version: Version 26.2
  • iOS Target: iOS 26.1
  • macOS Version: 26.1
  • Reproducibility: 100% consistent

Relevant Code

App Entry Point

import SwiftUI import AVFoundation

@main struct MyApp: App { init() { setupAudioSession() }

private func setupAudioSession() {
    do {
        try AVAudioSession.sharedInstance().setCategory(
            .playback,
            mode: .default,
            options: [.mixWithOthers]
        )
        try AVAudioSession.sharedInstance().setActive(true)
    } catch {
        print("Failed to setup audio session: \(error)")
    }
}

var body: some Scene {
    WindowGroup {
        ContentView()
    }
}

}### ContentView - Simplified Version

import SwiftUI

struct ContentView: View { @StateObject private var dataManager = DataManager.shared @StateObject private var settingsManager = SettingsManager.shared @StateObject private var sessionManager = SessionManager.shared

@State private var isSessionActive = false
@State private var selectedMode: Mode = .basic
@State private var showSettings = false

var body: some View {
    GeometryReader { geometry in
        ZStack {
            Color.gray.opacity(0.1)
                .ignoresSafeArea()
            
            VStack(spacing: 30) {
                // Header
                if !isSessionActive {
                    Text("My App")
                        .font(.largeTitle)
                        .padding(.top, 60)
                    
                    // Settings buttons
                    HStack {
                        SettingButton(title: "Mode", value: selectedMode.name) {
                            showSettings = true
                        }
                    }
                    .padding()
                }
                
                Spacer()
                
                // Main content
                Circle()
                    .fill(Color.blue.opacity(0.3))
                    .frame(width: 200, height: 200)
                
                Text(isSessionActive ? "Active" : "Ready")
                    .font(.title2)
                
                Spacer()
                
                // 🔴 THIS BUTTON DOESN'T RESPOND ON FIRST PREVIEW LOAD
                Button(action: {
                    print("Button tapped") // This doesn't print on first load
                    if isSessionActive {
                        endSession()
                    } else {
                        startSession()
                    }
                }) {
                    Text(isSessionActive ? "Stop" : "Start")
                        .font(.system(size: 18))
                        .foregroundColor(.white)
                        .frame(width: 160, height: 44)
                        .background(Color.blue)
                        .cornerRadius(22)
                }
                .padding(.bottom, 60)
            }
        }
    }
    .edgesIgnoringSafeArea(.all)
    .sheet(isPresented: $showSettings) {
        SettingsView()
    }
    .onAppear {
        initializeApp()
    }
}

private func startSession() {
    isSessionActive = true
    sessionManager.start()
}

private func endSession() {
    isSessionActive = false
    sessionManager.stop()
}

private func initializeApp() {
    dataManager.loadData()
    settingsManager.loadSettings()
}

}

// Simple supporting types enum Mode { case basic, advanced var name: String { self == .basic ? "Basic" : "Advanced" } }

struct SettingButton: View { let title: String let value: String let action: () -> Void

var body: some View {
    Button(action: action) {
        VStack {
            Text(title)
                .font(.caption)
            Text(value)
                .font(.subheadline)
        }
        .padding()
        .background(Color.gray.opacity(0.2))
        .cornerRadius(10)
    }
}

}

struct SettingsView: View { var body: some View { Text("Settings") } }

#Preview { ContentView() }

Manager Singleton Pattern

class DataManager: ObservableObject { static let shared = DataManager()

@Published var isReady = false

init() {
    setupManager()
}

func setupManager() {
    // Initialization code
}

func loadData() {
    isReady = true
}

}

class SettingsManager: ObservableObject { static let shared = SettingsManager() @Published var settings: [String: Any] = [:]

func loadSettings() {
    // Load settings
}

}

class SessionManager: ObservableObject { static let shared = SessionManager() @Published var isActive = false

func start() {
    isActive = true
}

func stop() {
    isActive = false
}

}## What I've Tried

All of the following did NOT fix the issue:

  1. ✅ Clean Build Folder & Derived Data
  2. ✅ Restart Xcode
  3. ✅ Remove .edgesIgnoringSafeArea(.all)
  4. ✅ Simplify Button action closure
  5. ✅ Reduce number of @StateObject dependencies
  6. ✅ Remove AVAudioSession setup from init
  7. ✅ Try .buttonStyle(PlainButtonStyle())
  8. ✅ Add explicit .contentShape(Rectangle()) to Button
  9. ✅ Test on different simulators

Suspected Factors

  • Multiple @StateObject singletons (3-5 managers)
  • AVAudioSession configured in App init
  • GeometryReader + ZStack layout
  • Preview state initialization timing issue
  • .sheet() modifiers attached to the view

Observations

  • Real device/simulator: Works perfectly
  • Xcode Preview (first load): Button completely unresponsive
  • After switching files: Everything works normally
  • After cleaning Preview: Problem comes back

Questions

  1. Is this a known Xcode Preview bug?
  2. Are there special considerations for multiple @StateObject in Previews?
  3. Could AVAudioSession initialization block Preview interactivity?
  4. Is there a way to force Preview to fully refresh its state?
  5. Should singleton managers be initialized differently for Previews?

Any insights would be greatly appreciated! This is driving me crazy since the actual app works fine - it's purely a Preview issue affecting development workflow.


Additional Info: The workaround (switching files) is consistent but tedious. I've seen similar issues reported online but no clear solution yet.

You've managed to put some of the code into code blocks, but not all of it.

When you submit your post, you know you can look at it, see that it's not formatted correctly, edit the post, and correct it, right?

Okay, well, I'll have a look and see if I can help.

I've tried this on macOS 15.7.3 Sequoia with Xcode 26.1.1 (17B100).

It works fine. The button taps work perfectly in Preview, in the Simulator, and on-device (iPhone 17 Pro Max with iOS 26.2).

Thanks for testing! Interesting that it works fine on your end.

I'm still experiencing the issue consistently on my setup:

  • macOS 26.1 (Build 25B78)
  • Xcode 26.2
  • M4 MacBook Pro, 16GB RAM

Specific Behavior (Updated)

When I first open ContentView and start Preview:

  1. UI renders perfectly ✅
  2. Button tap → Action executes (console prints work ✅), but UI doesn't update
    • @State variables change (verified in debugger)
    • Console logs appear
    • But the view doesn't re-render to reflect state changes
  3. Switch to any other file, then back → UI updates work normally
  4. Happens every time after cleaning or restarting Preview

So it's not that buttons don't work - they do, but Preview doesn't refresh the view when state changes on first load.

Questions

  1. Do you have multiple @StateObject singletons in your ContentView?
  2. Have you seen Preview needing a "refresh" to pick up state changes?

The issue only affects Preview - simulator and device work perfectly. It's like Preview's view update mechanism needs to be "activated" first.

Any ideas? Thanks! 🙏

Update: Issue Resolved! 🎉

Thanks again for testing on your setup! Your feedback helped me realize this might be an environment-specific issue.

Root Cause Found

After extensive debugging, I identified two key problems:

  1. Performance bottleneck during initialization: A utility manager was making repeated I/O calls on every UI update, flooding the console with 100+ log statements on first load.

  2. SwiftUI subscription mechanism timing: In Xcode Preview (specifically on macOS 26.1 + Xcode 26.2), when the main thread is heavily loaded during ContentView initialization, the @State → View update subscription doesn't properly establish.

Solution

Part 1: Performance optimization

  • Added caching for expensive operations
  • Removed excessive logging (100+ lines → 3 lines)
  • Pre-loaded resources instead of loading on-demand

Part 2: Preview-specific fix struct ContentView: View { @State private var refreshTrigger: Int = 0

var body: some View {
    // ... content
    .id(refreshTrigger)  // Force rebuild when id changes
    .onAppear {
        // Delay 0.5s to re-establish SwiftUI subscriptions
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            refreshTrigger += 1
        }
    }
}

}This forces Preview to rebuild the view after initialization completes, properly establishing the update subscriptions.

Result

✅ Preview works immediately after 0.5s initialization ✅ All buttons and state updates work correctly ✅ Real device/simulator completely unaffected ✅ No more file-switching workaround needed

Version Notes

Since it worked fine on your setup, this issue likely only affects certain Xcode/macOS versions. The performance optimization benefits all versions, while the .id() fix is harmless even where the bug doesn't exist.

Thanks for your help debugging this! The combination of performance issues + Preview environment quirks was tricky to isolate.

SwiftUI @State Updates Not Reflecting in UI Until View Reconstruction (Xcode Preview & Device)
 
 
Q