I'm learning SwiftUI and working on a iOS app project with SwiftUI where I have used Core Data for persistent data storage. I've created a global view model and injected it into the environment from top so that I can access it in within my app from anywhere.
App.swift
@main
struct AppName: App {
@StateObject var viewModel = AppViewModel()
var body: some Scene {
WindowGroup {
HomeScreenView()
.environmentObject(viewModel)
}
}
I've created @Published
arrays which holds the existing objects of core data entities. I use them to display values inside my view.
ViewModel.swift
class AppViewModel: ObservableObject {
@Published var entityAs: [EntityA] = []
@Published var entityBs: [EntityB] = []
@Published var entityCs: [EntityC] = []
...
init() {
//load data in variables
loadEntityAs()
loadEntityBs()
loadEntityCs()
...
}
func loadEntityAs() {
let request = NSFetchRequest<EntityA>(entityName: EntityNames.EntityA.rawValue)
do {
self.entityAs = try CoreDataManager.shared.context.fetch(request)
} catch let error {
print("Unable to fetch entity data: \(error.localizedDescription)")
}
}
...
I've used singleton for persistent data and call it when I've to save data. Persistence.swift
class CoreDataManager {
static let shared = CoreDataManager() //Singleton
let context: NSManagedObjectContext
let container: NSPersistentContainer
private init() {
container = NSPersistentContainer(name: "App_Name")
container.loadPersistentStores { description, error in
if let error = error {
print("Error loading core data: \(error)")
}
}
self.context = container.viewContext
}
func save() {
do {
if context.hasChanges {
try context.save()
}
} catch let error {
print("Error saving Core Data: \(error.localizedDescription)")
}
}
func delete(_ entity: NSManagedObject) {
context.delete(entity)
save()
}
}
I'm now creating views where user can modify the existing values of entities, for e.g. changing name. I can think of 2 options. What's the best practice here?
Creating a function called
fetchEntityA(name: String)
to fetch the required entity using its name. Then modify its value and then callingCoreDataManager.shared.save()
Looping over
@Published var entityAs
and getting the required entity followed by changing its value and then saving it.
I'm not sure if option 2 will work or not. I've used option 1 in my last project and it works fine. But I'm not sure if I'm overdoing stuff here.
Also, is this method of using @Published var entityAs: [EntityA]
good over using @FetchedResults
inside SwiftUI Views? I've not used @FetchedResults
before and I'm not sure how I should compare these 2 approaches.
Current Setup that I'm thinking of (option 1): EntityDetailsView.swift
struct EntityDetails: View {
@EnvironmentObject var vm: AppViewModel
let entity: EntityA
@State var name = ""
@State var type = ""
var body: some View {
VStack {
Text("Entity Details")
TextField(name, text: $name)
TextField(type, text: $type)
Button("Save") {
vm.updateEntityA(entity, newName: name, newType: type)
}
}
.onAppear {
self.name = entity.name ?? ""
self.type = entity.type ?? ""
}
}
}
I've previously used the first option, i.e. creating a function which will fetch required entity, modify its value and saving it. It works fine. But I want to explore other ideas, since writing these functions of fetching every entity I've in my app and writing update functions for each entity is a bit annoying. There must be a more optimal way of doing this stuff.