Jackster
Jackster

Reputation: 101

SwiftUI - Deleting from a Core Data NSSet - @FetchRequest with .onDelete()

I am working my way through, the free and publicly available Hacking With Swift course. Unfortunately, the chapter on Core Data does not explain how to remove an object in an NSSet, used in a one to many relationship. I have looked at the documentation and tried to work out how to use the Generated accessors, with little success. If someone would be able to explain how to do this, it would be much appreciated as I am most interested to know!!
One To Many Relationship

What I have tried so far and failed!!!


 func deleteCandy (at offsets: IndexSet) {
    for offset in offsets {
        let candy = countries[offset].candyArray[offset]
        moc.delete(candy)
    if self.moc.hasChanges{
        try? moc.save()
        }
    }
}

}

The Class:

extension Country {

@nonobjc public class func fetchRequest() -> NSFetchRequest<Country> {
    return NSFetchRequest<Country>(entityName: "Country")
}

@NSManaged public var fullName: String?
public var wrappedFullName: String {
    fullName ?? "Unknown Full Name"
}
@NSManaged public var shortName: String?
public var wrappedShortName: String{
    shortName ?? "Unknown Short Name"

}
public var candyArray:[Candy] {
    let set = candy as? Set<Candy> ?? []
    return set.sorted {
        $0.wrappedName < $1.wrappedName
    }
}


@NSManaged public var candy: NSSet?

}

// MARK: Generated accessors for candy extension Country {

@objc(addCandyObject:)
@NSManaged public func addToCandy(_ value: Candy)

@objc(removeCandyObject:)
@NSManaged public func removeFromCandy(_ value: Candy)

@objc(addCandy:)
@NSManaged public func addToCandy(_ values: NSSet)

@objc(removeCandy:)
@NSManaged public func removeFromCandy(_ values: NSSet)

}

The ContentView @Environment(.managedObjectContext) var moc @FetchRequest(entity: Country.entity(), sortDescriptors: []) var countries: FetchedResults

var body: some View {

    VStack {
        List {
            ForEach(countries, id: \.self) { country in
                Section(header: Text(country.wrappedFullName)) {
                    ForEach(country.candyArray, id: \.self) { candy in
                        Text(candy.wrappedName)
                    }
                }
            }.onDelete(perform: deleteCandy)
        }

        Button("Add") {
            let candy1 = Candy(context: self.moc)
            candy1.name = "Mars"
            candy1.origin = Country(context: self.moc)
            candy1.origin?.shortName = "UK"
            candy1.origin?.fullName = "United Kingdom"

            let candy2 = Candy(context: self.moc)
            candy2.name = "KitKat"
            candy2.origin = Country(context: self.moc)
            candy2.origin?.shortName = "UK"
            candy2.origin?.fullName = "United Kingdom"

            let candy3 = Candy(context: self.moc)
            candy3.name = "Twix"
            candy3.origin = Country(context: self.moc)
            candy3.origin?.shortName = "UK"
            candy3.origin?.fullName = "United Kingdom"

            let candy4 = Candy(context: self.moc)
            candy4.name = "Toblerone"
            candy4.origin = Country(context: self.moc)
            candy4.origin?.shortName = "CH"
            candy4.origin?.fullName = "Switzerland"

            try? self.moc.save()
        }
    }

}

func deleteCandy (at offsets: IndexSet) {
    for offset in offsets {
        let candy = countries[offset].candyArray[offset]
        moc.delete(candy)
    if self.moc.hasChanges{
        try? moc.save()
        }
    }
}

} The UI

Upvotes: 4

Views: 1596

Answers (2)

Jackster
Jackster

Reputation: 101

In the end, this solution passing in a closure from DMG worked really well:

Well explained solution here.

func deleteCandies(at offsets: IndexSet, from country: Country) {
    for offset in offsets.sorted().reversed() {
        let candyToDelete = country.candyArray[offset]
        country.removeFromCandy(candyToDelete)
        moc.delete(candyToDelete)
    }
    if moc.hasChanges{
        try? moc.save()
    }
}



VStack {
        List {
            ForEach(countries, id: \.self) { country in
                Section(header: Text(country.wrappedFullName)) {
                    ForEach(country.candyArray, id: \.self) { candy in
                        Text(candy.wrappedName)
                    }
                    .onDelete(perform: { offsets in
                        self.deleteCandies(at: offsets, from: country)
                    })
                }
            }
        }

Upvotes: 1

Lukas
Lukas

Reputation: 1

I stumbled across the same problem and solved it using the removeFromCandy(_ value: Candy) function generated from the NSManagedObject subclass.

    var body: some View {

        VStack {
            List {
                ForEach(countries, id: \.self) { country in
                    Section(header: Text(country.wrappedFullName)) {
                        ForEach(country.candyArray, id: \.self) { candy in
                            Text(candy.wrappedName)
                        }.onDelete {indexSet in
                            let deleteItem = self.country.candyArray[indexSet.first!]
                            self.country.removeFromCandy(deleteItem)
                            self.moc.delete(deleteItem)

                            do {
                                try self.moc.save()
                            } catch {
                                print(error)
                            }               
                        }
                    }
                }
            }

            Button("Add") {
                let candy1 = Candy(context: self.moc)
                candy1.name = "Mars"
                candy1.origin = Country(context: self.moc)
                candy1.origin?.shortName = "UK"
                candy1.origin?.fullName = "United Kingdom"

                try? self.moc.save()
            }
        }

    }

Upvotes: 0

Related Questions