UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Failed to call designated initialiser on NSManagedObject class

Forums > Swift

Hi I have a simple test app "Books" that has 2 entities Author and Book. There is a 1:many relationship between Author and Book. In my AddBookView I have: @State var = Author()

When the app starts I get the message "Failed to call designated initializer on NSManagedObject class 'Books.Author'. Google reveals that this statement creates an instance of Author using the standard init method and that this is not allowed and that the designated initializer (as the message says) must be used.

What does the designated initializer look like and where is it located?

struct AddBookView: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    @Environment(\.presentationMode) var presentationMode

    @State private var title = ""

    @State var author = Author()

    @FetchRequest(sortDescriptors: [], animation: .default)
    private var authors: FetchedResults<Author>

    var body: some View {
        Form {
            Section {
                HStack {
                    Text("Title:")
                        .fontWeight(.bold)
                    TextField("book title", text: $title)
                }
            }

            Section {
                HStack {
                    Text("Select an author:")
                        .fontWeight(.bold)
                    Picker("", selection: $author) {
                        ForEach(authors, id: \.self) { author in
                            Text(author.wrappedName)
                        }
                    }
                }
            }
        }
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button(
                    action: {
                        self.saveChanges()
                        presentationMode.wrappedValue.dismiss()
                    },
                    label: { Image(systemName: "square.and.arrow.down") }
                )
            }
        }
        .navigationTitle("Add Book Details")
    }

    func saveChanges() {
        let newBook = Book(context: self.managedObjectContext)
        newBook.uuid = UUID()
        newBook.title = self.title
        newBook.originBook = self.author

        if self.managedObjectContext.hasChanges {
            try? self.managedObjectContext.save()
        }
    }
}

3      

You can find the designated initializers here: https://developer.apple.com/documentation/coredata/nsmanagedobject

Basically, you have to initiate them in a valid ManagedObjectContext. Furthermore, Author is an object. You can't use @State with objects, only for value types. You use @ObservedObject for objects. You could make your author variable optional but I don't know how this works with your picker.

3      

Thank you for your reply.

I have read the documentation but I don't understand it: I don't understand when I would need to create and instance of an NSManagedObject class (and therefore use a designated initializer), or where I would use it or the syntax of the statement using it.

On the other hand, I have done more study on pickers and I have changed my @State to use a value type and this has fixed the problem of the "Failed...". A downside of changing the @State is that the relationship statement in SaveChanges() is now broken, but this is a separate issue.

Here is the new AddBookView containing a couple of small but very significant changes:

struct AddBookView: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    @Environment(\.presentationMode) var presentationMode

    @State private var title = ""

    @State private var selectedAuthor = ""                             <-- much better!

    @FetchRequest(sortDescriptors: [], animation: .default)
    private var authors: FetchedResults<Author>

    var body: some View {
        Form {
            Section {
                HStack {
                    Text("Title:")
                        .fontWeight(.bold)
                    TextField("book title", text: $title)
                }
            }

            Section {
                HStack {
                    Text("Select an author:")
                        .fontWeight(.bold)
                    Picker("", selection: $selectedAuthor) {
                        ForEach(authors, id: \.self) { author in
                            Text(author.wrappedName).tag(author.wrappedName)
                        }
                    }
                }
            }
        }
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button(
                    action: {
                        self.saveChanges()
                        presentationMode.wrappedValue.dismiss()
                    },
                    label: { Image(systemName: "square.and.arrow.down") }
                )
            }
        }
        .navigationTitle("Add Book Details")
    }

    func saveChanges() {
        let newBook = Book(context: self.managedObjectContext)
        newBook.uuid = UUID()
        newBook.title = self.title
        newBook.originBook = self.author                                  <-- broken: (value of type 'AddBookView' has no member 'author')

        if self.managedObjectContext.hasChanges {
            try? self.managedObjectContext.save()
        }
    }
}

2      

You need to use an initializer when you create a completely new object, like your Book in your saveChanges method (this is one of the designated initializers). You don't need to do that for your authors because you already fetched the existing authors with your FetchRequest and they're already Author objects. I guess your struggle is to choose the right author with your picker from your FetchRequest.

See here and here for further information.

3      

Hi @Hatsushira

Thanks for your input and I ticked your first reply as your 2 comments ( 1. use @ObservedObject for objects and 2. use @State for value types) were like light bulbs going off and were instrumental in solving my problems.

I think I have only a fingernail grip on designated initializers, but it will be enough to move forward.

The author picker works perfectly now. I also wrote a BookDetailView and a BookEditView passing the author name in the BookDetailView into the selectedAuthor of the BookEditView.

I fixed the broken code in saveChanges - I don't know if it's a hack, but it works.

    func saveChanges() {
        let newBook = Book(context: self.managedObjectContext)
        newBook.uuid = UUID()
        newBook.title = self.title

        if let relatedAuthor = authors.first(where: { $0.name == selectedAuthor }) {
            newBook.originBook = relatedAuthor
        }

        if self.managedObjectContext.hasChanges {
            try? self.managedObjectContext.save()
        }
    }
All problems SOLVED!!

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

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

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.