EnvironmentObject Causes SwiftUI App to Crash When Launched in the Background

I recently encountered a difficult-to-diagnose bug in an app that I'm working on, and I thought I would share it with the Apple Developer community.

The app that I'm working on is an iOS app that uses Core Location's Visit Monitoring API, and it is essential that the app is able to process incoming visits while running in the background. However, after real-world testing, I discovered several crash reports which were difficult to understand, as SwiftUI symbols are not symbolicated on debug builds.

Eventually I discovered that the app was crashing when calling an @EnvironmentObject property of the root ContentView from that view's body when the app was launched directly into the background from not running at all.

After creating a small test app to isolate the problem, I discovered that any environment object declared in the App struct and referenced from the root ContentView causes the crash, with the exception message: "Fatal error: No ObservableObject of type ArbitraryEnvObject found. A View.environmentObject(_:) for ArbitraryEnvObject may be missing as an ancestor of this view."

It seems that when a SwiftUI app is launched in the background, the ContentView's body is executed, but the environment is not initialized. I searched through as much documentation as I could, but could not find any information about how this should be handled, so I think it's a bug in SwiftUI. I have filed a Feedback Assistant bug report.

The current workaround is to convert my ObservableObject into an object that conforms to the new @Observable protocol, add it to the scene as an Environment Value with the .environment(_:) modifier rather than the .environmentObject(_:) modifier, and declare the @Environment property on the view as optional. The object will still be missing when the app is launched in the background, but the optional property can be handled safely to prevent a crash.

Attached to this post is a sample project that demonstrates the issue and the workaround.

And finally, I'd like to hear from anyone who knows more about the expected behavior of background launches of SwiftUI apps, and whether or not there's something I should be doing completely differently.

I'm not able to directly attach a zip archive to this post, so here's an iCloud link to the sample project: BackgroundEnvObjCrash.zip

Post not yet marked as solved Up vote post of squirepinto Down vote post of squirepinto
641 views
  • I am seeing this exact same problem with my SwiftData app. I am using a .modelContext from the root container, instead of an .environmentObject or .environment() but all other symptoms are the same.

Add a Comment

Replies

I'm seeing the same issue but my app is launched in the background using the WatchConnectivity framework. I think this is a clear bug. Did you file a radar?

  • Yes, I filed a report via Feedback Assistant. The report's ID is FB13524420. It is still open, with no recent similar reports listed. I haven't received any updates from Apple.

Add a Comment

I'm finding this exact situation, in an app being background-launched in response to CloudKit attempting to update my SwiftData-based storage after the watchOS variant synchronizes a new record upstream, and the iOS app attempts to persist that record.

I will look at the environment object usage in my ContentView.

To my knowledge, there is no way to avoid instantiating a ContentView in a SwiftUI app, right? For instance, handling background functionality without any UI.

Another developer is finding something similar. https://stackoverflow.com/questions/78265564/background-crash-swiftdata-swiftui-one-time-initialization-function-for-empty

  • I was able to avoid instantiating the root ContentView by adding an @Environment(.scenePhase) property to the App struct, and then wrapping the ContentView in an "if" block that checked if case .background = scenePhase. However, I wasn't comfortable with this, because I thought it might have unintended consequences when moving from foreground to background. onChange(of: scenePhase) doesn't seem to be called when launching into the background, so I stuck with using optional Environment values.

Add a Comment

Same here. Starting with iOS 17.4, my app waking in the background would crash when accessing an invalid modelContext environment variable. Even moving the app into the background with recent SwiftData changes (especially changes to Relationships) caused the same crash. My guess is that an autoSave is triggered? Easier to reproduce on iPad.

My workaround, based on the helpful discussion above....

  • Create a globally accessible modelContext and modelContainer. In my case I created a singleton "DataCenter" object created in the first line of the app's init.
  • Remove ALL @Environment(.modelContext) private var modelContext from your project
  • Add the modelContainer modifier to the WindowGroup, in my case WindowGroup{...}.modelContainer(DataCenter.shared.container)
  • Use that global context directly when needed, such as DataCenter.shared.context.insert(...)

Half measures didn't work for me, such as just removing the modelContext environment variable from the root view. Had to **** it entirely in the project.