sum only values with specified bool with core data - swift

I have a tableview with values. The database is made with core data. You can set the values to true or false. I only want to sum the values with true. To sum the values i have this code.
func printData() {
//Shorthand Argument Names
//let request: NSFetchRequest<Gegenstand> = Gegenstand.fetchRequest()
//let records = try! context.fetch(request) as [NSManagedObject]
let sum = ViewController.liste.reduce(0) { $0 + ($1.value(forKey: "gewicht") as? Double ?? 0) }
print("Gesamtgewicht: \(sum) kg")
gewicht = sum
if gewicht > 3500 {
gewichtLabel.textColor = .red
gewichtLabel.text = "\(gewicht) kg"
}
}
I tried it with an if-function but i don't know to use it with core data.

Create a coreData fetchRequest with isValue=true,
Calculate the sum of return fetchRequest
func fetchAllWithTrue() -> [Gegenstand] {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: Gegenstand)
fetchRequest.predicate = NSPredicate(format: “isValue== YES")
do {
let fetchedObjects = try coreDataManager.context.fetch(fetchRequest) as? [Gegenstand]
return fetchedObjects
} catch {
print(error.localizedDescription)
return [Gegenstand]()
}
}

You can do all of it in Core Data if you want. Filter for true filtering with an NSPredicate, and have Core Data calculate the sum using NSExpression.
First set up the fetch request to get only entries where the property is true and make sure it returns dictionary-type results (I don't know what your boolean is called, so here I'm calling it flag. Put your property name there):
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Gegenstand")
fetchRequest.resultType = .dictionaryResultType
fetchRequest.predicate = NSPredicate(format: "flag = true")
Then set up an NSExpressionDescription that can get the sum of gewicht values:
let sumExpression = NSExpression(format: "sum:(gewicht)")
let sumExpressionDescription = NSExpressionDescription()
sumExpressionDescription.expression = sumExpression
sumExpressionDescription.resultType = .double
sumExpressionDescription.name = "gewichtSum"
What this does is create a new kind of property that Core Data understands where the value is the sum the values of gewicht and is named gewichtSum. Fetch requests know how to use that kind of property. You do it like this:
fetchRequest.propertiesToFetch = [ sumExpressionDescription ]
Since the fetch uses dictionaryResultType, running the fetch returns an array of dictionaries. It's an array because fetch always returns an array, but here there's only one entry. The dictionary in that array entry has a key called gewichtSum for a Double property that was calculated from the expression above. You get the value like this:
do {
let result = try context.fetch(fetchRequest)
if result.count > 0,
let sumInfo = result[0] as? [String:Double],
let gewichtSum: Double = sumInfo["gewichtSum"] {
print("Sum: \(gewichtSum)")
}
} catch {
...
}
The print statement above prints the sum of all gewicht values where flag is true. It only includes true values for flag because of the NSPredicate, and it contains the sum because of the expression description.

Related

How does case insensitive sort work in Swift or other languages?

So, I am fetching some data from CoreData and I am using NSSortDescriptor to sort it in ascending order (I've tried other methods too). What happens is that the lowercase string with same text comes first. As per my understanding, in ASCII, uppercase strings come first ('A' starting at 65) and then lowercase strings ('a' starting at 97) and if we consider this, uppercase strings should come first when sorting in ascending order.
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true, selector: #selector(NSString.caseInsensitiveCompare(_:)))
let entities = CoreDataManager.getData(entityName: "Entity", predicate: nil, sortDescriptor: sortDescriptor) as? [Entity]
In my CoreDataManager class, I have the following methods.
class CoreDataManager: NSObject {
static func getManagedObject() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.managedObjectContext
}
static func getData(entityName: String, predicate: NSPredicate? = nil, sortDescriptor: NSSortDescriptor? = nil) -> [NSManagedObject] {
var resultsManagedObject: [NSManagedObject] = []
do {
let managedObject = getManagedObject()
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
if let descriptor = sortDescriptor {
request.sortDescriptors = [descriptor]
}
if predicate != nil {
request.predicate = predicate
}
let results = try managedObject.fetch(request)
resultsManagedObject = results as! [NSManagedObject]
} catch {
print("There was an error retrieving data")
}
return resultsManagedObject
}
}
I've also tried sorting using sorted method of arrays but that was also giving the same output as using NSSortDescriptor.
let arr = ["bamboo", "document", "BAMBOO", "DOCUMENT"]
print(arr.sorted { $0.lowercased() < $1.lowercased() }) // Output ~> ["bamboo", "BAMBOO", "document", "DOCUMENT"]
Why is sorting working like this? Is it the same in other languages?
It's not sorting lowercase first. It's comparing uppercase and lowercase as equal. But obviously, in an array, something has to come first. If a sorting algorithm is "stable", then if two elements compare equal, the one that appears first in the input will also appear first in the output. If the algorithm is "unstable" they might come out in any random order. It looks like you're getting a stable sort (or just getting lucky); since your example input has lowercase values before their corresponding uppercase values, the output does too.

how to save mixed sort?

From the list in which the words are located, I need to display them in a mixed state.
To do this, I added a shuffle sort field to the entity
func setShuffleWords(id: UUID?) -> [CoreWord]? {
guard let id = id else { return nil }
let requestWords = NSFetchRequest<NSFetchRequestResult>(entityName: "CoreWord")
requestWords.predicate = NSPredicate(format: "ANY dictionary.id == %#", id as CVarArg)
var coreWords: [CoreWord]?
if let result = try? managedContext.fetch(requestWords) as? [CoreWord] {
let words = result.shuffled()
for (index, value) in words.enumerated() {
if value.value(forKey: "shuffle") != nil {
value.setValue(index, forKey: "shuffle")
}
}
coreWords = words
}
save()
return coreWords
}
func getShuffleWords(id: UUID?) -> [CoreWord]? {
guard let id = id else { return nil }
let requestWords = NSFetchRequest<NSFetchRequestResult>(entityName: "CoreWord")
let sort = NSSortDescriptor(key: "shuffle", ascending: true)
requestWords.predicate = NSPredicate(format: "ANY dictionary.id == %#", id as CVarArg)
requestWords.sortDescriptors = [sort]
do {
let result = try managedContext.fetch(requestWords)
return result as? [CoreWord]
} catch let error {
fatalError(error.localizedDescription)
}
}
But, the problem is that if I mix up another list where this word is, then the value is rewritten and it will change its position everywhere.
How to keep mixed order in a specific list?
In this situation you need to replace your many-many relationship with an intermediate entity. Let’s call that entity a Listing. It will represent the listing of a particular word in a particular group, so each Listing should have a to-one relationship to CoreWord and a to-one relationship to CoreGroup. In each case the inverse (which you might call “listings”) should be to-many. The Listing entity should have (at least) one attribute, shuffle, which represents the sequence of a particular word on a particular group.
Life generally gets more complicated, because you have to create and manage these intermediate objects rather than just working the Word and Group objects and their relationships.

CoreData sum in Swift

I have a CoreData entity Transaction with a decimal amount attribute. I want to get the sum of all amounts filtered by a predicate.
Altohugh there are solutions using loops I want to do it directly in CoreData because of performance (and understanding NSExpression).
The following code works as expected but, as I said, is not what I am looking for
// Easy way: Fetch all and use `value(forKeyPath:)`
let transactionFetch = NSFetchRequest<NSManagedObject>(entityName: CoreDateWrapper.Entity.Transaction)
var results: [NSManagedObject] = []
do {
results = try wrapper.context.fetch(transactionFetch)
} catch {
print("Error: \(error.localizedDescription)")
}
print((results as NSArray).value(forKeyPath: "#sum.\(CoreDateWrapper.Attribute.amount)") as! Decimal)
Now I want to use NSExpression, but the fetch result always is an empty array.
// Setup the expression and expression-description
let amountExpression = NSExpression(forKeyPath: CoreDateWrapper.Attribute.amount)
let sumExpression = NSExpression(forFunction: "sum:", arguments: [amountExpression])
let sumDescription = NSExpressionDescription()
sumDescription.expression = sumExpression
sumDescription.expressionResultType = .decimalAttributeType
sumDescription.name = "sum"
// Setup the fetchRequest to only get the sum.
// I expect a dictionary as a result instead of a `NSManagedObject`
let sumFetch = NSFetchRequest<NSDictionary>(entityName: CoreDateWrapper.Entity.Transaction)
sumFetch.propertiesToFetch = [sumDescription]
sumFetch.resultType = .dictionaryResultType
// Fetch the sum
var sumResult: [String: Decimal]? = nil
do {
let array = try wrapper.context.fetch(sumFetch)
if let res = array.first as? [String: Decimal] {
sumResult = res
} else {
print("Wrong type for result")
}
} catch {
print("Error fetching result: \(error.localizedDescription)")
}
// Output the sum
if let sum = sumResult?["sum"] {
print("Total: \(sum)")
}
This always prints Wrong type for result because array is empty.

Swift: Core data trouble with to-many relationship and mutableOrderedSetValueForKey

I'm having trouble understanding why my ordered set is only saving 1 entry.
my code is as follows:
let newInovice = NSEntityDescription.insertNewObjectForEntityForName("Invoice", inManagedObjectContext: managedObjectContext) as! InvoiceMO
let itemDetail = NSEntityDescription.insertNewObjectForEntityForName("InvoiceDetail", inManagedObjectContext: managedObjectContext) as! InvoiceDetailMO
// Data Entry
// Add invoice number to Invoice
newInovice.invoiceNumber = getInvoiceNum()
newInovice.date = invoiceDate
// Create mutable set to add details
for items in details {
itemDetail.detail = items[PropKeys.itemDescription]
let item = items[PropKeys.itemPrice] ?? "0"
let itemDouble = { return Double(item) ?? 0 }()
itemDetail.price = itemDouble
let quantity = items[PropKeys.itemQuantity] ?? "1"
let quantityInt = { return Int(quantity) ?? 0 }()
itemDetail.quantity = quantityInt
print("This is the item detail before insertion: \(itemDetail)")
newInovice.mutableOrderedSetValueForKey("invoiceDetails").addObject(itemDetail)
subtotal += itemDouble * Double(quantityInt)
}
print("details.Count = \(details.count)")
print("Start Mutable \(newInovice.invoiceDetails?.count) End Mutable Set")
// Save the Data
do {
try newCustomer.managedObjectContext?.save()
} catch {
print(error)
}
I've read through the docs and it seems like I'm doing this correctly but it only adds one entry to the ordered set even though I've iterated through the array of objects.
Here is my debugger showing the entries before they are added and the count of the managed object after adding the objects.
debuggerWindow
my relationships are set up as follows:
relationships
You are only creating one InvoiceDetail outside your for loop. Obviously, you will have to create one for each detail that is contained in the data array.

Swift - How to save entries to core data in a loop

I have 2 entities: Series and Season. A Series can have multiple seasons, so I set the relationship type to "to many". season_counter is an Int Array, containing the amount of episodes.
let newSeries = NSEntityDescription.insertNewObjectForEntityForName("Series", inManagedObjectContext: self.context!) as! Series
for var i = 0; i < season_counter.count; ++i{
let newSeason = NSEntityDescription.insertNewObjectForEntityForName("Season", inManagedObjectContext: self.context!) as! Season
newSeason.value = i+1
newSeason.episodes = season_counter[i]
newSeries.setValue(NSSet(object: newSeason), forKey: "seasons")
do {
try context?.save()
} catch _ {
}
}
While debugging I noticed, that season_counter stores the correct values. When I display the results, I have only the last season stored (for example 13 episodes, seasons.count is 1):
do {
let fetchRequest = NSFetchRequest(entityName: "Season")
fetchRequest.predicate = NSPredicate(format: "series = %#", series)
try seasons = context?.executeFetchRequest(fetchRequest) as! [Season]
print(seasons.count)
print(seasons[0].episodes)
} catch {
}
Any tips to solve this?
In each pass through your loop, you're throwing away the previous value of seasons and replacing it with a set that contains only one season (the newly created newSeason):
newSeries.setValue(NSSet(object: newSeason), forKey: "seasons")
Instead of doing that, build up a set of all of the newSeason instances, and then call setValue:forKey: once, with that set.
Your life will be easier, by the way, if you create subclasses of NSManagedObject and get out of the "value for key" business.
newSeries.setValue(NSSet(object: newSeason), forKey: "seasons")
With this line you always create a new set of seasons containing exaclty one Season object - newSeason.
Try to do that:
var prevSeasons = newSeries.valueForKey("seasons")
prevSeasons.insert(newSeason)
newSeries.setValue(prevSeasons, forKey: "seasons")