BillThomas
BillThomas

Reputation: 55

How to re-use the same FetchRequest, rather than the same request in several views?

I use the same fetch request in multiple top level views under ContentView. I mean the same entity / predicate etc.

Previously I tried doing the request once in ContentView and passing it as an array through several layers of views. However, this stopped changes, deletes etc, being propagated across other views.

I just wondered if there is another approach which would work ?

I'm thinking that some kind of singleton approach might work, but I'm worried about the performance implications, however this might outweigh having to run the request several times.

Also, I wondered about passing a request results rather than array? However having to pass this around seems ugly.

Upvotes: 0

Views: 557

Answers (1)

EmilioPelaez
EmilioPelaez

Reputation: 19884

You can use the Environment to pass your models to children without having to passing an array through several layers of views. You start by creating your own EnvirnomentKey

public struct ModelEnvironmentKey: EnvironmentKey {
    public static var defaultValue: [Model] = []
}

public extension EnvironmentValues {
    var models: [Model] {
        get { self[ModelEnvironmentKey] }
        set { self[ModelEnvironmentKey] = newValue }
    }
}

public extension View {
    func setModels(_ models: [Model]) -> some View {
        environment(\.models,  models)
    }
}

I like using ViewModifiers to setup my environment, following the Single-Responsibility Principle:

struct ModelsLoaderViewModifier: ViewModifier {
    @FetchRequest(entity: Model(), sortDescriptors: [])
    var models: FetchedResults<Model>
    
    func body(content: Content) -> some View {
        content
            .setModels(Array(models))
    }
}

extension View {
    func loadModels() -> some View {
        modifier(ModelsLoaderViewModifier)
    }
}

I then would add this modifier pretty high on the view hierarchy.

@main
struct BudgetApp: App {
    @ObservedObject var persistenceManager = PersistenceManager(usage: .main)
    let startup = Startup()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .loadModels()
        }
    }
}

Now ContentView can read from the environment:

struct ContentView: View {
    @Environment(\.models) var models
    
    var body: some View {
        List {
            ForEach(models) { model in
                Text(model.name)
            }
        }
    }
}

Upvotes: 2

Related Questions