Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Bind .environment Globally to All SwiftUI Previews?

I have a global DataManager that holds an NSManagedObjectContext:

class DataManager {
    static let shared = DataManager()
    var viewContext: NSManagedObjectContext
    // ... Other code
}

Everything works fine within the application itself. However, I'm running into issues with SwiftUI previews. Currently, I'm setting the .environment for each preview like this:

#Preview {
    let exampleModel = ExampleModel.create(context: DataManager.shared.viewContext)
    
    return NavigationStack {
        ExampleView(model: exampleModel)
    }
    .environment(\.managedObjectContext, DataManager.shared.viewContext)
}

This approach becomes tedious when dealing with multiple views. I'm looking for a way to set .environment(\.managedObjectContext, DataManager.shared.viewContext) globally for all SwiftUI previews.

Is there a more efficient way to achieve this?

I've done some research and it seems feasible to create a wrapper for previews. However, I haven't found a solution that allows me to continue using the #Preview macro.


1 Answers

You can create a ViewModifier with all your injection and sample code.

extension View {
    func globalInjection() -> some View {
        modifier(GlobalInjectionVM())
    }
}
struct GlobalInjectionVM: ViewModifier {
    let context: NSManagedObjectContext = DataManager.shared.viewContext
    func body(content: Content) -> some View {
        content
            .environment(\.managedObjectContext, context)
    }
}

Then all you need to do is call the func at every preview.

#Preview {
    SampleView()
        .globalInjection()
}

If you want to step this up you can easily create preview objects with a protocol

protocol SampleProviderProtocol {
    static func preview() -> Self
}

extension Item: SampleProviderProtocol {
    static func preview() -> Self {
        let new = Self(context: DataManager. shared.viewContext)
        new.timestamp = Calendar.current.date(byAdding: .day, value: (-10...10).randomElement()!, to: Date())
        return new
    }
}

Then you can dynamically create them in the ViewModifier

struct GlobalInjectionVM: ViewModifier {
    let context: NSManagedObjectContext = DataManager.shared.viewContext
    //Declare the types that you want to create samples 
    let types: [SampleProviderProtocol.Type] = [Item.self]
    func body(content: Content) -> some View {
        content
            .environment(\.managedObjectContext, context)
            .task {
                //Iterate over the types
                for type in types {
                    //Create 4 preview objects
                    for _ in 0...3 {
                        _ = type.preview()
                    }
                }
            }
    }
}

Make sure you are using a "Preview" context when working with Canvas you can to it by setting the url to null before loading the store.

container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")

PreviewModifier has been introduced in

iOS 18.0+ iPadOS 18.0+ Mac Catalyst 18.0+ macOS 15.0+ tvOS 18.0+ visionOS 2.0+ watchOS 11.0+

It is very similar to ViewModifier but has the advantage that you can use traits for injection.

#Preview(traits: .coreDataNull) { // <--- Here
    GlobalPreviewInjectionSampleView()
}

Here is a sample of the new modifier.

struct CoreDataPreview: PreviewModifier {
    let types: [SampleProviderProtocol.Type] = [Item.self]

    static func makeSharedContext() async throws -> NSManagedObjectContext {
        //Something that includes
        //container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        return .init(concurrencyType: .mainQueueConcurrencyType)
    }
    
    typealias Context = NSManagedObjectContext

    
    func body(content: Content, context: Context) -> some View {
        content
            .environment(\.managedObjectContext, context)
            .task {
                //Iterate over the types
                for type in types {
                    //Create 4 preview objects
                    for _ in 0...3 {
                        _ = type.preview()
                    }
                }
            }
    }
}

extension PreviewTrait where T == Preview.ViewTraits {
    @MainActor static var coreDataNull: Self = .modifier(CoreDataPreview())
}
like image 167
lorem ipsum Avatar answered Oct 19 '25 11:10

lorem ipsum



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!