'Swiftui - Calling Coredata in ObservableObject
I am new to SwiftUI and one thing I am struggling to understand is how do we call CoreData in ObservableObject?
I have the following code in place.
SimpleTodoModel.xcdatamodeld Inside there is a simple entities name: Task
Main Application
import SwiftUI
@main
struct NewApp: App {
let persistentContainer = CoreDataManager.shared.persistentContainer
var body: some Scene {
WindowGroup {
ContentView().environment(\.managedObjectContext, persistentContainer.viewContext)
}
}
}
CoreDataManager and ContentView
import Foundation
import CoreData
class CoreDataManager {
let persistentContainer: NSPersistentContainer
static let shared: CoreDataManager = CoreDataManager()
private init() {
persistentContainer = NSPersistentContainer(name: "SimpleTodoModel")
persistentContainer.loadPersistentStores { description, error in
if let error = error {
fatalError("Unable to initialize Core Data \(error)")
}
}
}
}
struct ContentView: View {
@EnvironmentObject var obs : observer
@State private var title: String = ""
@State private var selectedPriority: Priority = .medium
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(entity: Task.entity(), sortDescriptors: [NSSortDescriptor(key: "dateCreated", ascending: false)]) private var allTasks: FetchedResults<Task>
private func saveTask() {
do {
let task = Task(context: viewContext)
task.title = title
task.priority = selectedPriority.rawValue
task.dateCreated = Date()
try viewContext.save()
} catch {
print(error.localizedDescription)
}
}
var body: some View {
NavigationView {
VStack {
//making a view and calling coredata values which is working perfectly
}
}
}
}
Here is where i struggle, calling coredata outside of the view in a class/ObservableObject
class observer : ObservableObject{
//How should i call the coredata instead?
//Say i want to change a piece of value in what i have saved above inside one of the Task record?
}
Solution 1:[1]
Presuming you want to do this with a strict MVVM architecture, here is a demonstration of how it may work for you. You will need a data model, and a view model.
You data model essentially mimics the Core Data model:
struct TaskModel: Identifiable {
private var task: Task
init(task: Task) {
self.task = task
}
var id: NSManagedObjectID { // Identifiable conformance
task.objectID
}
var dateCreated: Date {
task.dateCreated
}
var priority: Priority {
task.priority
}
var title: String {
task.title
}
}
extension TaskModel {
init?(taskID: NSManagedObjectID, context: NSManagedObjectContext) {
do {
guard let task = try context.existingObject(with: taskID) as? Task else { return nil }
self.task = task
} catch {
return nil
}
}
}
Your view model would then look something like this:
@MainActor
class TaskListViewModel: NSObject, ObservableObject {
@Published var tasks: [TaskModel] = []
private let fetchResultsController: NSFetchedResultsController<Task>
private let context: NSManagedObjectContext
init(_ compoundPredicate: NSCompoundPredicate, category: CategoryModel? = nil, showTasks: ShowTasks, context: NSManagedObjectContext) {
self.context = context
let fetchRequest = Task.fetchRequest()
super.init() // NSObject conformance to use the delegate
fetchResultsController.delegate = self
do {
try fetchResultsController.performFetch()
guard let tasks = fetchResultsController.fetchedObjects else { return }
self.tasks = tasks.map(TaskModel.init)
} catch {
}
}
func deleteTask(_ task: TaskModel) {
do {
guard let taskObject = try context.existingObject(with: task.id) as? Task else { return }
try taskObject.delete()
} catch {
}
}
func save(_ task: TaskModel) {
do {
guard let taskObject = try context.existingObject(with: task.id) as? Task else { return }
try taskObject.save()
} catch {
}
}
}
The delegate will cause the view model to update when the Core Data model changes:
extension TaskListViewModel: NSFetchedResultsControllerDelegate {
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
guard let tasks = controller.fetchedObjects as? [Task] else { return }
self.tasks = tasks.map(TaskModel.init)
}
}
In your view, I would initialize it as a @StateObject, unless this is a common view model, in which case the first initialization should be as a @StateObject, and then injected appropriately, something like this:
@StateObject private var vm: TaskListViewModel
init(vm: TaskListViewModel) {
_vm = StateObject(wrappedValue: vm)
}
This code has not been tested, so there may be some syntax errors...
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Yrb |
