BLACK FRIDAY: Save 50% on all my Swift books and bundles! >>

How to properly add a new item to an NSSet related to another entity in CoreData

Forums > SwiftUI

@JFS  

I hope I can make myself clear. I have two CoreData entities User and SecretItem. One User can hold an NSSet of SecrteItems. I set the relationship to Many at the user with destination SecretItem and to One at SecretItem with destination User. I use a two column NavigationSplitView in my ContenView (macos app) performing the @FetchRequest only here, not in the Detail-View:

struct ContentView: View {

    @Environment(\.managedObjectContext) var moc

    @FetchRequest(sortDescriptors: [SortDescriptor(\.name)])
    private var users: FetchedResults<User>

    @State private var selectedUserID: User.ID?
    @State private var defaultUserID: User.ID?

    var body: some View {
        NavigationSplitView {
            SidebarView(selection: selection)

        } detail: {
            if selection.wrappedValue != nil {
                SecretDetailView(user: selectedUser)
            } else {
                Text("Please select a user")
            }
        }
    }

    private var selection: Binding<User.ID?> {
        Binding(get: { selectedUserID ?? defaultUserID }, set: { selectedUserID = $0 })
    }

    private var selectedUser: User {
        users.filter({ $0.id == selection.wrappedValue }).first!         // <-- passed user to the @ObservedOject user in Detail-View
    }
}

Everything works fine. The users are listed in the Sidebar-View. A selected user is passed to ObservedObject var user: User in the Detail-View showing the secretItem details. When I try to add another secretItem the app crashes pointing to the Swift app-file with Thread 1: EXC_BREAKPOINT (code=1, subcode=0x19d0b56b4) I don't know how to add a new secretItem to the NSSet of the selected User. Any guidance?

here is my Detail-View with the add-func:

struct SecretDetailView: View {

    @Environment(\.managedObjectContext) var moc
    @ObservedObject var user: User

    var secretItems: [SecretItem] {
        return user.secretItems
            .filter {
                searchText.isEmpty ? true : $0.userName.localizedCaseInsensitiveContains(searchText)
            }
            .sorted(using: sortOrder)
    }

    @State var searchText: String = ""
    @State private var selection = Set<SecretItem.ID>()

    @State var sortOrder: [KeyPathComparator<SecretItem>] = [
        .init(\.userName, order: SortOrder.forward)
    ]

    // ... View Body stuff
}

extension SecretDetailView {

    // only to have somthing to work with!
    private func addSecretItems() {
        let usernames = ["quingflack23", "norton", "k-flex@hotmail.com", "kia-lokie@gmx.net", "san-silver", "quarks123", "user01", "wonder woman"]
        let passwords = ["1Jhg&%5", "123password", "noFun$3", "/(zhgTrF", "QTZ121!", "Wonder123)(", "awesomePW"]

        let chosenName = usernames.randomElement()!
        let chosenPW = passwords.randomElement()!

        Task { await addSecretItem(name: chosenName, pw: chosenPW)}
    }

    private func addSecretItem(name: String, pw: String) async {

        await moc.perform {
            let secretItem          = SecretItem(context: moc)
            secretItem.id           = UUID()
            secretItem.userName     = "\(name)"
            secretItem.password     = "\(pw)"
            secretItem.user         = user      // <-- here an error is thrown. I'm not sure if this is right, the SecretItem has an @NSManaged public var user: User created  

            user.addToSecretItems(secretItem)       // <-- this also throws an error. I try to use the created func in the User class
        }
        try? moc.save()
    }
}

2      

@JFS  

I found my mistake myself. The issue was to be find in the @NSManagedObjectClass User. Within the created swift file User+CoreDataProperties.swift the generated accsessors for SecretItem was defiend wrong. I changed it as follows and than it worked as wanted:

User+CoreDataProperties.swift:

// MARK: Generated accessors for secretItems
extension User {
    ...
    @objc(addSecretItems:)
    @NSManaged public func addToSecretItems(_ values: Set <SecretItem>)
    ...
}

So my code within SecretDetailView was ok to have a new secretItem added to the user entity:

private func addSecretItem(name: String, pw: String) async {

        await moc.perform {
            let secretItem          = SecretItem(context: moc)
            secretItem.id           = UUID()
            secretItem.userName     = "\(name)"
            secretItem.password     = "\(pw)"
            secretItem.user         = user   // <-- here I'm still not sure if I need this

            print(secretItem)
            print(user)
            user.addToSecretItems(secretItem)
        }
        try? moc.save()
    }

2      

Usually, you shouldn't need secretItem.user = user , the provided function addToSecretItems should do this for you. But if you do, you wouldn't need to call addToSecretItems on user.

But you could try and test it out.

2      

Save 50% in my WWDC sale.

SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

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.