Summary
Hi! I’m looking for the best way to get a Core Data entity – selected by the user – everywhere in my SwiftUI app. The app has a fairly complex view structure with multiple tabs, modals and navigation stack levels.
As you can see below, the model is structured in a way that all entities are in some way related to one entity (Home). It’s possible to have multiple Home entities, though. The user can then select which Home they want to see and the whole app reloads it’s content based on that selection. The selection is also saved in User Defaults to be persisted during launches.
So, as I basically need the selected Home in (almost) every view, I don’t want to pass it down as a parameter of every single view that is loaded.
Core Data Structure
Code Structure
Currently I’m using a DataHandler()
manager class that (creates) and holds the selected Home as a @Published
variable. This variable is an optional which allows me to handle the onboarding if users haven’t created a Home yet.
@main
struct MyApp: App {
let persistenceController = PersistenceController.shared
// My custom manager class
@StateObject var dataHandler = DataHandler()
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
.environmentObject(dataHandler)
}
}
}
class DataHandler: ObservableObject {
// Variable that holds the user selected Home entity
@Published var selectedHome: Home?
// User Defaults
@AppStorage("homeID") private var homeID: UUID?
init() {
// Check if User Default exists
if homeID != nil {
// Search for Home with User Default ID
selectedHome = fetchHomeBy(id: homeID!)
// Check if Home with ID exists
if selectedHome == nil {
loadFallbackHome()
}
} else {
// No UserDefault
loadFallbackHome()
}
}
// more stuff …
}
To access the selected Home in the given view I’m then using an @EnvironmentObject
. I then want to filter my other entity types for this very selection in a @FetchRequest
or @SectionedFetchRequest
. The fetch requests may also filter for additional variables or custom sorting.
struct EventsTabView: View {
@EnvironmentObject var dataHandler: DataHandler
@SectionedFetchRequest private var events: SectionedFetchResults<String, Event>
init() {
// Fetching Events of selected Home that aren’t archived
_events = SectionedFetchRequest(entity: Event.entity(),
sectionIdentifier: \.startYear,
sortDescriptors: [NSSortDescriptor(keyPath: \Event.startDate, ascending: false)],
predicate: NSPredicate(format: "eventHome == %@, isArchived != true", DataHandler().selectedHome!))
}
var body: some View {
// …
}
}
Problem
The problem I’m currently running into is that I can’t access the manager class in the @FetchRequest
. I need this to filter the Meters and Events for the selected Home and additional filter parameters, though!
I’ve learned that @EnvironmentObjects
can’t be used in a custom init()
. And the way I currently handle the predicate of the fetch request isn’t great either as it always creates a new instance of DataHandler()
.
I’ve tried putting the fetch requests in computed variables that returns an array of the entities I need. The problem then is that my UI doesn’t correctly update when adding/deleting/editing the data.
I also thought about using a derived Home.id attribute in every other entity. This way I could only store the selected Home ID in the @Published var
. I guess that’s better performance wise? Though that still begs the question how to access this variable in the @FetchRequests
then?
So my questions would be:
- How can I access an app wide variable in the dynamic fetch requests of my
init()
? - Is there a better design to handle a user selection like this
instead of loading the whole entity into a
@Published
variable of an@EnvironmentObject
class? - Is using computed variables to store the fetch request results worse (performance wise) than doing the fetch request in the views
init()
?
PS: I’m fairly new to SwiftUI, so I would be so thankful if someone can point me into a direction or can point out a better solution. :)