0

The question is this: how do you group Core Data objects by attribute and then perform simple calculations (let's say, sum) on them?

I've found a few similar answers for this (like this one), but none seem conclusive and all are geared up towards Swift (whereas I am working on SwiftUI).

For example, I have a Core Data model that looks like this:

extension Items {

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

    @NSManaged public var id: UUID?
    @NSManaged public var cost: Int16
    @NSManaged public var type: String
}

In the Home() view, I'd like to group by type and then find the total amount paid (i.e. sum of cost) for all objects in each type.

So far, I've tried making a special group() function (a bit like in this question), but get the error message "Cannot find 'i' in scope".

struct Home: View {
    
    @Environment(\.managedObjectContext) private var viewContext
    
    @FetchRequest(entity: Items.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Items.type, ascending: true)]) var items: FetchedResults<Items>

var body: some View {
        ForEach(group(items), id: \.self) { [i] in
            Text("\([i].cost.reduce(0,+))")
           }
      }
}

func group(_ result : FetchedResults<Items>)-> [[Items]] {
        
    return Dictionary(grouping: result) { $0.type }
            .sorted(by: {$0.key < $1.key})
            .map {$0.value}
        
    }

Surely this can't be so hard? Any help very welcome!

FPL
  • 456
  • 4
  • 21
  • 1
    You should not have brackets around the `i` in `[i] in`. Brackets like that *capture* a value, but you don't have a variable to capture -- you're just getting a parameter for the closure. Same think on the next line -- no brackets there either. There may be issues beyond this as well -- it would be good to see the source for `Clothing` – jnpdx Apr 13 '21 at 22:07
  • Thanks, I would love this to be the answer but when I try I get the message *"Value of type '[Items]' has no member 'cost'"*. `Clothing` should read `Items`, too (apologies, this was a mistake and I corrected it in the question). – FPL Apr 13 '21 at 22:10
  • 1
    Yeah, like I said, there may be additional issues. You're returning a multidimensional array from `group`, so you have to unwrap that first. Maybe you mean `i.map { $0.cost }.reduce(0,+)` – jnpdx Apr 13 '21 at 22:12
  • Nice, this worked perfectly! Feel free to submit this as an answer if you'd like :-) Many thanks in either case. – FPL Apr 13 '21 at 22:15

1 Answers1

1

You should not have brackets around the i in [i] in. Brackets like that capture a value, but you don't have a variable to capture -- you're just getting a parameter for the closure.

ForEach(group(items), id: \.self) { i in

On your next line, you're dealing with a multidimensional array. You can't access a property directly, but you could map first:

Text("\(i.map(\.cost).reduce(0,+))")

You could also structure this slightly differently and remove the map by doing:

i.reduce(0,{ $0 + $1.cost })
jnpdx
  • 45,847
  • 6
  • 64
  • 94