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

SOLVED: CoreData error: Failed to call designated initializer on NSManagedObject class

Forums > 100 Days of SwiftUI

I'm trying to instantiate a "Student" object to be shown on my editionView, over a modal sheet, like this:

.sheet(isPresented: $showingAdd) {
    StudentEditView(student: Student())
}

It works: sheet is presented, with the editionView prepared to add data, but I see on the console the message I've written on the title of this post.

I've tried to "force" this new object to be instantiated on the specific Core Data context:

.sheet(isPresented: $showingAdd) {
    StudentEditView(student: Student(context: moc))
}

But doing so results on instantiating "infinite objects" (or that's the result presented on screen).

Any suggestions to make this console error dissapear?

   

I'm not sure if I understood you correctly? With your sheet you want to add a new Student and not edit an existing one?

If it's the first one you can create the new Student object in the init method of the sheet itself. With sheets I think the problem is that you still have to add the moc as environmentObject.

StudentEditView()
  .environmentObject(moc)

Your problem with the "infinite" objects is probably the result of creating a new instance of Student everytime you open the modal but you don't discard it if you don't want to keep it. So in your modal you have to discard the changes (Rollback) if you just dismiss the sheet and you should add an explicit "Save"-Button to save the changes and keep the Student and then dismiss the sheet.

   

In fact, I am using that editView both for my students to be edited and to be added, depending on an "isEditing" property to control what to do in each case.

  • On the first case (edition), I arrive from Student's detailed view with this sheet

    .sheet(isPresented: $showingEditionSheet) {
      StudentEditView(student: student, isEditing: true)
    }

    as I define the @ObservedObject on editView like this:

    @ObservedObject var student: Student
  • On the second case, I arrive to editView this way:

    .sheet(isPresented: $showingAdd) {
      StudentEditView(student: Student(), isEditing: false)
    }

    And, efectively, I wouldn't even need to pass any new Student to editView, but as it's an @ObservedObject property, it can't be optional, and I have to pass something. On this second case editView is used only to enter Student data with textFields. I save later to CoreData with an external method, which creates itself the new Student instance:

let newStudent = Student(context: moc)
...
try? moc.save()

About "infinit students" result, it't not exactly the way you say. If I pass the context on editing call:

.sheet(isPresented: $showingAdd) {
            StudentEditView(student: Student(context: moc), isEditing: false)
}

The result is as seen on the attached image. And with this console message:

ForEach<FetchedResults<Student>, Optional<UUID>, ModifiedContent<ModifiedContent<NavigationLink<VStack<TupleView<(StudentCellView, ModifiedContent<ModifiedContent<Rectangle, _FrameLayout>, _EnvironmentKeyWritingModifier<Optional<Color>>>)>>, StudentDetailView>, _PaddingLayout>, ContextMenuModifier<ZStack<ModifiedContent<Button<_ConditionalContent<Label<Text, Image>, Label<Text, Image>>>, StyleContextWriter<MenuStyleContext>>>>>>: the ID nil occurs multiple times within the collection, this will give undefined results!

https://dropover.cloud/55f3de

I cancel the adittion, but the list is that of the image, altough when quitting app and open it again all dissapear. It seems that if I instantiate a new student just on editView opening, the forEach responsible for presenting the list with all of them (behind the modal editView) is adding one after another "new", infinitely. So maybe the best approach would be not passing any student at all when creating a new object. But @ObservedObject can't be optional 🥴

   

Yes, but your Student in the initializer of StudentEditView can be optional.

init(student: Student?) {
  if let student {
    self.student = student
    // more code here
  } else {
    self.student = Student(context:moc)
    // more code here
  }
  // do whatever you need to do
}

with this approach your isEditing may not be needed, either. But I'm sure you would need to pass moc via EnvironmentObject.

Your second case never works because you can't create a NSManagedObject without a NSManagedObjectContext. This doesn't work. See the documentation for NSManagedObject and the valid initializers.

   

But then, where I declare my @ObservedObject property? This throw errors:

@ObservedObject var student: Student

    init(student: Student?) {
        if let student {
            self.student = student
        } else {
            self.student = Student(context: moc)
        }
    }

Variable 'self.student' used before being initialized. Return from initializer without initializing all stored properties.

Here, the beginig of editView:

import SwiftUI

struct StudentEditView: View {
@Environment(\.managedObjectContext) var moc
@ObservedObject var student: Student

    init(student: Student?) {
        if let student {
            self.student = student
        } else {
            self.student = Student(context: moc)
        }
    }

    @State private var name = ""
    @State private var phone = ""
    @State private var mail = ""
    @State private var birthdate = Date()
   ...

    var body: some View {
    ...
    }

   

Gna, this s?cks. Problem is that you can't access moc in the initializer. The explanation is here. My apologies. I forgot about that. This is also the explanation of your phantom Students.

You could pass moc in the init as well. Then you don't need the environment. I don't have a better solution.

struct StudentEditView: View {
    var moc: NSManagedObjectContext
    @ObservedObject var student: Student

    init(student: Student?, moc: NSManagedObjectContext) {
        self.moc = moc
        if let student {
            self.student = student
        } else {

            self.student = Student(context: moc)
        }
    }

    var body: some View {
        // Code here
    }
}

   

I think this is useful enogh to reorganize my view. Thanks 

   

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.