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

Answered by DTS Engineer in 788508022

Thanks for submitting the bug report @squirepinto, Another alternative workaround would be to specify a defaultValue and use the EnvironmentKey. This way there's an instantiated default value available if text is accessed in the background and you can also set the environment value for a view and all its subviews.


@Observable
final class ArbitraryEnvValue {
    var text = "This is text"
}

private struct MyEnvironmentKey: EnvironmentKey {
    static let defaultValue: ArbitraryEnvValue = ArbitraryEnvValue()
}

@main
struct BackgroundEnvObjCrashApp: App {
    private let locationManager = LocationManager.shared
    @State private var envVal = ArbitraryEnvValue()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .environment(\.myCustomValue, envVal)
    }
}


extension EnvironmentValues {
    var myCustomValue: ArbitraryEnvValue {
        get { self[MyEnvironmentKey.self] }
        set { self[MyEnvironmentKey.self] = newValue }
    }
}

struct ContentView: View {
    
    @Environment(\.myCustomValue) private var envVal: ArbitraryEnvValue
    var body: some View { 
     .....
     }
}

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?

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 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.

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.

Thanks for submitting the bug report @squirepinto, Another alternative workaround would be to specify a defaultValue and use the EnvironmentKey. This way there's an instantiated default value available if text is accessed in the background and you can also set the environment value for a view and all its subviews.


@Observable
final class ArbitraryEnvValue {
    var text = "This is text"
}

private struct MyEnvironmentKey: EnvironmentKey {
    static let defaultValue: ArbitraryEnvValue = ArbitraryEnvValue()
}

@main
struct BackgroundEnvObjCrashApp: App {
    private let locationManager = LocationManager.shared
    @State private var envVal = ArbitraryEnvValue()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .environment(\.myCustomValue, envVal)
    }
}


extension EnvironmentValues {
    var myCustomValue: ArbitraryEnvValue {
        get { self[MyEnvironmentKey.self] }
        set { self[MyEnvironmentKey.self] = newValue }
    }
}

struct ContentView: View {
    
    @Environment(\.myCustomValue) private var envVal: ArbitraryEnvValue
    var body: some View { 
     .....
     }
}

Hi, just a cross reference to my answer on this thread.

tl;dr: I could solve it by moving the view modifier from the WindowGroup to the ContentView. I have not tested it for the .environment view modifier.

Non crashing example:

import SwiftData
import SwiftUI

@main
struct ExampleApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .modelContainer(for: [Item.self])
        }
    }
}
EnvironmentObject Causes SwiftUI App to Crash When Launched in the Background
 
 
Q