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.
Related
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.
Please help me understand the logic reading data from the Firestore document, if one of the values is an array. I tried other answers here and sources but never came to a simple working way and understand clearly. Firestore document structure — example. And Swift class targets for saving (conditional):
struct MyStruct {
var name: String
var pages: Int
}
let part1 = [MyStruct]()
let name1 = ""
let pages1 = 0
let part2 = [MyStruct]()
let name2 = ""
let pages2 = 0
func readFirestore() { }
What should the document reader function look like to add data to existing targets in the class? Thanks in advance for any help in improving my understanding!
They helped to deal with familiar, thank you for what they are. As expected, everything is simple. But for the beginner there is nothing more difficult than simplicity 😁
func readFirestore() {
self.db.collection("example").document("book").getDocument { (document, error) in
if error == nil {
if document != nil && document!.exists {
//get all document data
guard let documentData = document!.data() else {return}
//get value-array for key "part1"
let element = documentData["part1"] as? [Any] //print -> Optional([name1, 100])
//get first element in array
guard let nameDB = element?[0] as? String else {return} //print -> name1
guard let pagesDB = element?[1] as? String else {return} //print -> 100
//append in class
part1.append(MyStruct(name: nameDB, pages: pagesDB))
name1 = nameDB
pages1 = pagesDB
}
}
}
}
Here is my Array of Dictionary,
var myArrayOfDict = [["vegetables": ["CARROT","BEANS"], "fruits": ["APPLE","MANGO","BANANA"], "letters":["A","B","C","D"],"numbers":["ONE","TWO","THREE"],"shapes":["SQUARE","RECTANGLE","CIRCLE"]]]
How do i get the desired output, actually i need to get random selected elements of the specified range ...(i.e) when i need 3 elements randomnly from dictionary as like,
[["fruits": ["APPLE","MANGO","BANANA"],"shapes":["SQUARE","RECTANGLE","CIRCLE"],"numbers":["ONE","TWO","THREE"]]]
When i need just 2 elements randomnly like,
[["shapes":["SQUARE","RECTANGLE","CIRCLE"],"fruits": ["APPLE","MANGO","BANANA"]]]
Thanks in Advance,
Here is one solution using randomElement().
func randomSelection(from dict: [String: [String]], count: Int) -> [String: [String]] {
guard !dict.isEmpty else { return [:] }
var result = [String: [String]]()
for i in 0..<count {
let element = dict.randomElement()! //We know dictionary is not empty
result[element.key] = element.value
}
return result
}
The above solution might return less elements in a dictionary than expected if the same element is returned more than once from randomElemnt(). If this should be voided the below solution should work
func randomSelection(from dict: [String: [String]], count: Int) -> [String: [String]] {
guard !dict.isEmpty else { return [:] }
guard dict.count > count else { return dict }
var result = [String: [String]]()
while result.count < count {
let element = dict.randomElement()!
if result[element.key] == nil {
result[element.key] = element.value
}
}
return result
}
Since the function takes a dictionary as the first argument the array needs to be looped over
for d in myArrayOfDict {
print(randomSelection(from: d, count: 2))
}
Array myArrayOfDict contains a single Dictionary. So, it doesn't make sense getting a random element from it.
As your example explains, you need to get random elements from the Dictionary itself.
So, you can use randomElement to get that working.
let myArrayOfDict = ["vegetables": ["CARROT","BEANS"], "fruits": ["APPLE","MANGO","BANANA"], "letters":["A","B","C","D"],"numbers":["ONE","TWO","THREE"],"shapes":["SQUARE","RECTANGLE","CIRCLE"]]
var elements = [String : [String]]()
let count = 2
for _ in 0..<count {
if let element = myArrayOfDict.randomElement() {
elements[element.key] = element.value
}
}
print(elements)
I want to store data into object form using swift language.The data structure of the data base is like
collection/
document/
collection/
document1/:
Invitess1(object) :
name :"santosh"
phone :1234567890
Invitee2(object) :
name :"sam"
phone:1234654768
.....
document 2/
Initee1(object) :
name:"red"
phone:4654343532
.......
is it possible to store data like this? if possible how to do it? i tried like this :
for var i in 0..<n { // n is no.of selected contacts
for var j in i...i {
print("amount is \(d[i])")
print("phone number is \(num[j])")
let dataToSave:[String: Any] = ["name" :"vijayasri",
"PhoneNumber":num[j],
"Amount": d[i],
]
}
}
var ref:DocumentReference? = nil
ref = self.db.collection("deyaPayUsers").document("nothing").collection("Split").addDocument(data: dataToSave){
error in
if let error = error {
print("error adding document:\(error.localizedDescription)")
} else {
print("Document ades with ID:\(ref!.documentID)" )
}
}
}
But it doesn't work. How to do it..
Your example code is never going to work as intended since dataToSave is overwritten every iteration of the j loop. Your inner j loop probably has a typo at i...i
To store multiple objects in one document, create the document in Swift with multiple objects in it. Since you know how to encode your object as [String:Any], just take those dictionaries combine into a larger [String:Any]document.
I would change your code to be more like:
var dataToSave: [String:Any] = []()
for var i in 0..<n { // n is no.of selected contacts
var inProcess: [String:Any] = []()
for var j in i...i {
print("amount is \(d[i])")
print("phone number is \(num[j])")
let detail: [String: Any] = ["name" :"vijayasri",
"PhoneNumber":num[j],
"Amount": d[i]]
inProcess["NextKey\(j)"] = detail
}
dataToSave["SomeKey\(i)"] = inProcess
}
var ref:DocumentReference? = nil
ref = self.db.collection("deyaPayUsers").document("nothing").collection("Split").addDocument(data: dataToSave){
error in
if let error = error {
print("error adding document:\(error.localizedDescription)")
} else {
print("Document ades with ID:\(ref!.documentID)" )
}
}
}
This is my JSON data, how can I get src data in 0 in pickArray?
"pickArray" : "{\"0\":{\"src\":\"https:\/\/fb-s-d-a.akamaihd.net\/h-ak-xpl1\/v\/t1.0-9\/p720x720\/18010403_1525007564199498_8009700960533638318_n.png?oh=25dbc9c1522dcfdd1d15cdd3e8c0c7da&oe=59997685&__gda__=1502470695_f212ade003e9b1c4ddc6a3ab6cc9e7e7\",\"width\":720,\"height\":720}}"
If I do it like this:
let dataArray = json["pickArray"]
print("dataArray = ",dataArray)
dataArray = {"0":{"src":"https://fb-s-d-a.akamaihd.net/h-ak-xpl1/v/t1.0-9/p720x720/18010403_1525007564199498_8009700960533638318_n.png?oh=25dbc9c1522dcfdd1d15cdd3e8c0c7da&oe=59997685&__gda__=1502470695_f212ade003e9b1c4ddc6a3ab6cc9e7e7","width":720,"height":720}}
But if I do it like this, show null:
let srcArray = dataArray["0"]
print("srcArray = ",srcArray)
I'm using swift3.0
Its looks like that with key pickArray you are having JSON response in String so get that string and convert it data and get JSON from it and then get src from it.
let stringResponse = json["pickArray"].stringValue
if let data = stringResponse.data(using: .utf8) {
let pickArray = JSON(data: data)
//Now access the pickArray to get the src
var sortedKeys = [String]()
if let allKeys = pickArray.dictionaryObject {
sortedKeys = Array(allKeys.keys).sorted { $0.compare($1, options: .numeric) == .orderedAscending }
}
for key in sortedKeys {
print(pickArray[key]["src"].stringValue)
print(pickArray[key]["width"].intValue)
print(pickArray[key]["height"].intValue)
}
}
let srcArray = dataArray["0"].dictionaryObject!
print("srcArray = \(srcArray)")
Now you can access element of "0" value as like below. Hope this work for you.
let jsonScr = JSON(srcArray)
let srcURL = jsonScr["scr"].stringValue