This is how my Firebase looks like
Firebase ScreenShot
This is my code
override func viewDidLoad() {
var sum = 0
var counter = 0
super.viewDidLoad()
ref = Database.database().reference()
ref.child("WaterAdded").observe(.childAdded, with: { (snapshot) in
if let totalwateradded = snapshot.value as? [Int]{
while counter < (totalwateradded.count) {
var newValue = totalwateradded[counter]
sum += newValue
}
self.totalAdded.text = "\(sum)"
}
})
}
I want to grab all the number in Firebase and display the sum. But it display nothing.
You cannot directly cast snapshot.value to [Int], instead if you want to get all objects in your node you should use
let ref = Database.database().reference()
ref.child("WaterAdded").observe(.childAdded, with: { (snapshot) in
if let water = snapshot.value as? String{
guard let waterAmount = Int(water) else{return}
sum += waterAmount
}
})
that while loop is not needed as this will give you all the values in the database.
Edit
In the database you can use Strings and Ints to store numbers. You store them as Strings. If you load them in Swift you have to conditionally cast the value to String (as? String). The problem, however is that you can not do any arithmetic operations on strings so using the Int(water) statement you can convert it to Int. This operation can give an integer from a string if it contains a number, but it can also fail (e.g. Int("two")) and therefore we use guard let to make sure we only proceed to the next line if it can successfully convert to an Int. Afterwards we just add the int value to sum and done.
try...
if let snap = snapshot.value as? Dictionary [Sting: Any], let totalWaterAdded =
snap["\(yourDictionaryKey)"] as? Int{
//enter your code...
}
Here's the answer that reads the node, sums the values and print it to console.
let ref = self.ref.child("WaterAdded")
ref.observeSingleEvent(of: .value, with: { snapshot in
var x = 0
for child in snapshot.children {
let val = (child as! DataSnapshot).value as! Int
x = x + val
}
print("sum: \(x)")
})
We're using observeSingleEvent of .value in this case as there's really no reason for firebase to iterate over each child when we can do it a lot faster in code. Also, we don't need to be notified of childAdded events.
Tossing a guard statement in the mix may be a good idea but as long as you are sure the values will only be Int's, it's good to go as is.
Tested with the following structure
WaterAdded
a: 0
b: 1
c: 2
d: 3
and the output is
6
Related
I have an app where users can rate films they have watched, and I want to be able to put these in a tableView. The rating is stored in Firestore, and I want to put both the KEY and value into a Struct so I can access it for the tableView.
However any site/tutorial/stack question I have seen only gets the Maps value, but not the key (in this case, the title name). I can access the value, but only by using the field key, but that is what I am trying to get (see attempt 1)
Struct:
struct Rating: Codable {
var ratedTitle: String
var ratedRating: Int
}
Variable:
var ratedList = [Rating]()
Load data function (attempt 1):
let dbRef = db.collection("Users").document(userID)
dbRef.getDocument { document, error in
if let error = error {
print("There was an error \(error.localizedDescription)")
} else {
if let docData = document!.data() {
let titleRating = docData["Title Ratings"] as? [String: Int]
let midnightMass = titleRating!["Midnight Mass"]
print("Rating given to Midnight Mass: \(midnightMass!) stars")
}
}
}
//Prints: Rating given to Midnight Mass: 2 stars
Also tried (but I don't know how to get this array onto a tableView and have the first index as the Title label, and the second index a Rating label for each movie in the array) attempt 2:
if let docData = document!.data() {
let titleRating = docData["Title Ratings"] as? [String: Int]
self.userRatedList = titleRating!
print("userRatedList: \(self.userRatedList)")
}
//Prints: userRatedList: ["Midnight Mass": 2, "Bly Manor": 5]
Attempt 3:
if let docData = document!.data() {
let titleRating = docData["Title Ratings"] as? [String: Int]
self.ratedList = [Rating(ratedTitle: <#T##String#>, ratedRating: <#T##Int#>)]
//Don't know what I would put as the ratedTitle String or ratedRating Int.
self.ratedList = [Rating(ratedTitle: titleRating!.keys, ratedRating: titleRating!.values)]
//Cannot convert value of type 'Dictionary<String, Int>.Keys' to expected argument type 'String'
//Cannot convert value of type 'Dictionary<String, Int>.Values' to expected argument type 'Int'
}
Firstly, I am not sure why you need the struct to conform to Codable?
Now, based off what I see, "Title Ratings" is a dictionary with a String key and an Int value. You are overcomplicating this. If you want to access the key and value of each element individually, use a for-in loop.
//Declare your global variable
var ratedList = [Rating]()
//If you are using an if let, there is not need to force unwrap
if let docData = document.data() {
if let userRatingList = docData["Title Ratings"] as? [String: Int] {
for (key, value) in userRatingList {
let rating = Rating(ratedTitle: key, ratedRating: value)
ratedList.append(rating)
}
//reload your tableView on the main thread
DispatchQueue.main.async {
tableView.reloadData()
}
}
}
I need to retrieve price value of each product in the cart child, but how should I retrieve it and sum the retrieved value together?
Picture of my Firebase database structure
let uid = Auth.auth().currentUser?.uid
refProduct = Database.database().reference().child("users").child(uid!).child("cart")
refProduct.observeSingleEvent(of: .value) { (snapshot) in
for cartchild in snapshot.children{
let snap = cartchild as! DataSnapshot
let key = snap.value
.....
}
}
I would not store the price as a string, but as a number. You might want to add another field with currency if needed.
guard let uid = Auth.auth().currentUser?.uid else { return }
var sum: Double = 0
refProduct = Database.database().reference().child("users").child(uid).child("cart")
refProduct.observeSingleEvent(of: .value) { (snapshot) in
for cartchild in snapshot.children{
let snap = cartchild as! DataSnapshot
let data = snap.value as? [String: Any]
let price = data["ProductPrice"] as? Double ?? 0
sum += price
}
print("Final sum: \(sum)")
}
Not really tested, but this is the idea
Arvidurs is correct about storing the price as an int and the currency as a string, but the reason the answer isn't working for you is that it doesn't address that you're not correctly retrieving the data you want in the first place.
You have your cart folder, and it contains two product folders whose properties you're trying to retrieve. You can't retrieve and unwrap the values contained in those two folders by just referencing the parent cart folder. You need to individually access each folder within cart:
Database.database().reference().child("users").child(uid).child("cart").child("-Lf59bkQ5X3ivD6ue1SA")
Database.database().reference().child("users").child(uid).child("cart").child("-Lf5MiEGU357HWTMbxv8")
However, for this to work, you'll need access to each products autoID value, so you'll need to be storing each new product's childByAutoID value into an array or a dictionary so that you have them all available to access whatever data you need.
You'll need to implement this as you're storing the new product to the cart folder. I don't know exactly how you're currently saving each product, but you'll need to do something like this when you create your reference that you'll be saving to:
let newProductRef = Database.database().reference().child("users").child(uid).child("cart").childByAutoId()
let autoID = newProductRef.key
At that point, you'll be able to store autoID however you choose, and you'll have access to everything within the cart folder, and you can loop through all of your autoIDs and get whatever data you need. Example:
func getCartPriceSum(finished: #escaping ([String : Double]) -> Void){
let myGroup = DispatchGroup()
var sum = Double()
var currency = String()
for autoID in autoIdArray{
myGroup.enter()
let productRef = Database.database().reference().child("users").child(uid).child("cart").child(autoID)
productRef.observe(.value) { (snapshot) in
guard
let snapshotValue = snapshot.value as? NSDictionary,
let productPrice = snapshotValue["ProductPrice"] as? Double,
let priceCurrency = snapshotValue["PriceCurrency"] as? String//assuming you've adopted Arvidurs' method of storing the price data
else {
print("productPrice/priceCurreny nil")
return
}
sum += productPrice
currency = priceCurrency
}
myGroup.leave()
}
let priceSum = [currency : sum]
myGroup.notify(queue: .main) {
finished(priceSum)
}
}
And you could call the function like this:
getCartPriceSum { (priceSum) in
//do whatever you want with the priceSum
}
The only thing left for you to figure out is how you want to store those autoIDs.
I am using firebase to retrieve a list of data then convert it to an NSDictonary array. I want to parse the data by a property e.g name
func getAllMyModels() {
if let e = email {
_ = ref.child("childName").queryOrdered(byChild: "email").queryEqual(toValue: e).observe(.value) { snapshot in
var dictionary = [NSDictionary]()
let children = snapshot.children
while let rest = children.nextObject() as? DataSnapshot, let value = rest.value {
dictionary.append(NSDictionary(dictionary: value as! [String: Any]))
}
let names = dictionary.flatMap {$0["name"]} // names print correct values
let set = Set(names)
print(set)
}
}
}
This code can't be complied the error is:
Showing Recent Messages
Command failed due to signal: Segmentation fault: 11
If i removed this line:
let set = Set(Array(names))
all works fine.
I also tested by replace it by this block
let ar = ["name1","name2"].flatMap { return $0 }
Set(ar)
No errors.
Not sure why? Who can tell, thanks!
EDIT: Even though the element in the array is String type but the names array is [Any], so the solution is
let names = dictionary.flatMap {$0["name"]} as! [String]
I think this errors occurs because the Array you generate from the dictionary with flatMap is an Array of Any and not a String Array, try to cast to a String like this:
...
let names = dictionary.flatMap {$0["name"] as? String}
let set = Set(Array(names))
...
Hope this help you
Cast names to [String]:
let names = dictionary.flatMap {$0["name"]} as! [String]
I m new to programming I just playing around in playground, I am trying to loop in a dictionary to do some calculation and add them into new dictionary however, I can only add one dictionary value back. I am not sure why. Can anymore tell me and point out the point?
var name:String?
var popDict: [String: Array<Double>]?
var FinalDict: [String: Array<Double>]?
var mean: Double?
var elementSideArr: Array<Double>?
var nameArray = ["Olivia": [1,2,2,2,1,2,1,0.0001,0,1,2], "Amber": [52,52,65,66,57,63,62,0.0001,0,0,0]]
var doubleArr = [Double]()
for (key, value) in nameArray{
let thisValue = value
let arrayS = thisValue.prefix(7)
let slice = arrayS[0...6]
let popSideArr = Array(slice)
let average: Double = (popSideArr as NSArray).value(forKeyPath: "#avg.self") as! Double
mean = average
let thatValue = value
let arraySS = thatValue.suffix(3)
let sslice = arraySS[8...10]
elementSideArr = Array(sslice)
elementSideArr?.insert(average, at: 0)
let dict = ["\(key)": elementSideArr!]
for (key,value) in dict{
popDict = dict
}
}
print(popDict!)
["Olivia": [1.5714285714285714, 0.0, 1.0, 2.0]] // It is the only output but it should be 2 items.
Problem is in your inner for loop you are initializing whole new dictionary to popDict also you don't need for loop if you are having single key and value pair with your dictionary.
Replace line of codes:
let dict = ["\(key)": elementSideArr!]
for (key,value) in dict{
popDict = dict
}
With:
popDict["\(key)"] = elementSideArr!
Note: No need to cast swift array to NSArray to calculate average you can simply use reduce to get total and then divide the total with array count.
let array = popSideArr as? [Int] ?? []
let res = Double(array.reduce(0, +)) / Double(popSideArr.count)
I'm trying to look for items equal or less than zero in my query like so.....
for zeroItems in snapshot.value as! NSDictionary where zeroItems.value["Value"] as! Int <= 0
I'm getting an Expected type error. Please explain and show me how to correct.
You may want to consider applying a filter to simplify the logic to only include elements less than zero. I've created a working example in the IBM Swift Sandbox.
import Foundation
// Construct the data structure with demo data
struct Snapshot {
let value: NSDictionary
}
let dict: NSDictionary = [ "a" : -1, "b" : 0, "c" : 1]
let snapshot = Snapshot(value: dict)
//Apply a filter
let zeroItems = snapshot.value.filter{Int($0.1 as! NSNumber) <= 0}
//View the result
print(zeroItems)
Almost certainly you've overused Any if you have to use this many as! casts in a single line. Create a custom struct for this type and convert the data to that. If you're passing around NSDictionary and using as! very much, you're fighting the system.
That said, to make this work you probably just need more parentheses. Something like:
for zeroItems in (snapshot.value as! NSDictionary) where (zeroItems.value["Value"] as! Int) <= 0
But this is horrible Swift, so avoid this if possible.
You interpreting snapshot.value as Dictionary, so zeroItems is a tuple of key and value.
As I understand you having array of dictionaries, and you want to filter them by "Value" key, right?
If so then you may use following code:
// dictionary with Value key, and Int value
let dict: [String: Any] = ["Value":-1]
// array of dictionaries
let value: [[String: Any]] = [dict, <**add here as many as you want**>]
// get only array of integeres
let onlyIntegers = value.flatMap { $0["Value"] as? Int }.filter {$0 <= 0 }
print(onlyIntegers)
// get array of dictionaries which passes <=0 check
let onlyLessOrEqualTo0 = value.filter { dict in
if let value = dict["Value"] as? Int {
return value <= 0
}
return false
}
print(onlyLessOrEqualTo0)
First of all, adding some parentheses will help in seeing where the problem is:
for zeroItems in myDict as! NSDictionary where (zeroItems.value["Value"] as! Int) <= 0
Now, we get "Type 'Any' has no subscript members". So the problem is that you haven't told Swift what the type is of zeroItems's value, which I think is a dictionary of , judging from your code:
This compiles for me:
for zeroItems in myDict as! Dictionary<String, Dictionary<String, Int>> where zeroItems.value["Value"]! <= 0
However, this is not pretty. You could probably get more readable code using filter as was suggested in another answer.