NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

SOLVED: All Core Data methods (CRUD) on a separate file.

Forums > 100 Days of SwiftUI

I'm trying to reorganize my code as suggested in https://www.hackingwithswift.com/books/ios-swiftui/introducing-mvvm-into-your-swiftui-project. So I'm dealing with moving all my Core Data methods (save, delete, update...) on a separate file to call that methods from the correspondent views.

I've tried creating functions taking parameters to be used to write data on CD. I instantiate there as well an instance of my DataController in order to get to my ManagedObjectContext:

import SwiftUI

class StudentCDMethods {

 func delete(student: Student) {
        let dataController = DataController()
        let moc = dataController.container.viewContext
        moc.delete(student)
        if moc.hasChanges {
            do {
                try moc.save() }
            catch {
                print(error.localizedDescription)
            }
        }
    }
    ...
}

But when trying to call this method, for example, from the view that takes this data, I don't find the way to invoke that method properly. Have I to instantiate this class in order to use its methods?

let studentCDMethods = StudentCDMethods()
 ...
studentCDMethods.delete(student: student)

But it doesn't work:

Cannot convert value of type '()' to expected argument type '() -> Void'

Is this the correct way to do it? Or maybe with an extension of "Student" entity? I've tried as well, without success. How then?

   

More details:

I've been able to invoke this method. The problem was that I was trying from inside a confirmation dialog button this way:

Button("Confirm", role: .destructive, action: studentCDMethods.delete(student: student))

Now, with this sintax, it works:

Button {
       studentCDMethods.delete(student: student)
           } label: {
               Text("Confirm")
           }

BUT. Now compiler says "An NSManagedObjectContext cannot delete objects in other contexts.". Yes: I'm trying to access the same context I use all along my app:

class StudentCDMethods {

@Environment(\.managedObjectContext) var moc

...

What am I missing?

   

Answering myself πŸ˜…. Just in case it's usefull for somebody:

I've passed as well, as a parameter, the context:

studentCDMethods.delete(moc: moc, student: student)

Now, it's working: the "environment" context is added on my app to MainView itself, so this class has no access to it.

In adittion: doing this way, it's possible to create, even better than a Class, an Extension of Student, with exactly the same estrategy, and using simply (wihtout having to instantiate nothing at all):

student.delete(moc: moc, student: student)

πŸ™ƒ

   

hi Γ“scar,

i use extensions on all Core Data objects to do simple things, such as nil-coalesce text or date fields, or to convert Int16 and Int values (so SwiftUI views need not have to do that), and i sometimes use a similar delete strategy ... i think that's a darn good idea.

although when you write

student.delete(moc: moc, student: student)

you don't have to over-do things.

for example, you don't have to ask a student instance to call its own delete function, and pass itself in as a parameter as well; and in fact, the student already knows its managed object context, so that need not be passed in either. the following syntax would then be a more compact extension.

extension Student {
  func delete() {
    let context = self.managedObjectContext // an optional
    context?.delete(self)
    try? context?.save()
  }
}

now you can simply ask a student to delete itself with student.delete().

hope that helps,

DMG

   

Thanks @delawaremathguy 😊.

I tried that way, but accesing the context was a problem. The way you give is more clear, and work perfectly.

But... how can I use the same strategy for saving? This code on Student extension:

let newStudent = Student(context: moc)

forces me to unwrap "moc" (as it's optional, as you say). I can't force (!) or coalesce (??) it, as it's a new object and does not have a context already. I have my context "injected" on environment, but as this is an extension and not a view, I can't access the usual way:

@Environment(\.managedObjectContext) var moc

   

For saving the same approach should work:

    func save() {
        let context = self.managedObjectContext // an optional
        try? context?.save()
    }

But you can just save from your dataController as well.

But this is not a valid approach for creating a new Student. This would mean that an already existing Student creates a new Student which probably isn't what you want.

You can write a public static function on your Student extension to create a new Student. You then have to pass the moc into the function, though.

public static func create(moc: NSManagedObjectContext) -> Student {
        return Student(context: moc)
    }

You then can create a new Student with:

newStudent = Student.create(moc: moc)

I don't see much benefit over newStudent = Student(context: moc) though.

Either way, for creating a new Student you have to pass the NSManagedObjectContext from an external source. In your example the already existing Student would be this external source.

The static function could be a good choice if you want to set specific parameters everytime you create a Student. So you don't have to write them everytime you create a Student.

   

OK. Thanks a lot!

   

Hacking with Swift is sponsored by Stream

SPONSORED Build Chat messaging quickly with Stream Chat. The Stream iOS Chat SDK is highly flexible, customizable, and crazy optimized for performance. Take advantage of this top-notch developer experience, get started for free today!

Click here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.