Firebase saving multiple child values to arrays - arrays

Looking at the structure of my firebase database:
I am attempting to save the values for every key for every cryptocurrency into separate arrays.
For example, the 'coinAmount' key, I would like to get an array looking like ["1.0,"2.0"], and 'coinName' as ["Bitcoin","Ethereum"] etc for each of the 5 keys.
My attempt at this:
let index = NSIndexPath(item: 0, section: 0)
if portfolioCoinFullNameString.count > 0 {
print("PortfolioVC: Number of Coins in Portfolio > 0")
let coinRef = ref.child("\(portfolioCoinFullNameString[index.row])")
coinRef.observe(DataEventType.value) { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
let value = snap.value
print("key = \(key) ,, value = \(value!)")
if key == "coinAmount" {
self.firebaseCoinAmountArray.append(Double(value as! String)!)
} else if key == "coinName" {
self.firebaseCoinNameArary.append(value as! String)
} else if key == "coinPrice" {
self.firebaseCoinPriceArray.append(Double(value as! String)!)
} else if key == "coinSymbol" {
self.firebaseCoinSymbolArray.append(value as! String)
} else if key == "coinTotalFiat" {
self.firebaseCoinTotalFiatArray.append(Double(value as! String)!)
} else {
print("PortfolioVC: Key does not match anything!")
}
}
}
} else {
print("PortfolioVC: Number of Coins in Portfolio !> 0")
}
This works for adding the first coin, but when i attempt to add the second one the 2 values are appended to the array so it contains 3, and if i add a third coin it append 3 values. Not sure why its looping through each coin?
EDIT:
I searched around StackOverflow and came across another method:
let index = NSIndexPath(item: 0, section: 0)
if portfolioCoinFullNameString.count > 0 {
print("PortfolioVC: Number of Coins in Portfolio > 0")
let coinRef = ref.child("\(portfolioCoinFullNameString[index.row])")
coinRef.observe(DataEventType.value) { (snapshot) in
let value = snapshot.value as? NSDictionary
let coinName = value?["coinName"] as? String ?? ""
self.firebaseCoinNameArary.append(coinName)
print("aaaaaa:\(self.firebaseCoinNameArary)")
}
} else {
print("PortfolioVC: Number of Coins in Portfolio !> 0")
}
But still have the issue of when adding any coin as second element in the array, the array contains 3x first coin added instead of just two entires of the first coin and second coin. I know i could do an if statement to check if the array already contains the name and if it does dont add it, but how would i do this with the number values as theres the possibility coins could have the same price.??
Edit2: Been thinking about it, would it work if i just observe the values and not for changes, save all of the to Arrays, populate the cells with them. Then if a user edits the 'coinAmount' value in the app, it updates in Firebase, but not the arrays. Then, upon user closing app/logging out, next time portfolio is shown it will re-pull the values from firebase which contains the updated numbers?
Edit3: Also, the nodes under portfolio called 'Bitcoin' etc, if i called them coin1, coin2, coin3 etc, how would i get that path and get the key and values for say coin3? Is there a way to just get the values for All the nodes under 'protfolio' in one go?
The idea is once I save all the firebase data to the arrays i will populate a tableview cell with all of the data.
Any help is appreciated :)
Thanks,
Jeremy

In order to do this don't use a for-loop use .childAdded
I will provide an example
database.ADD THE ENTIRE PATH.child("portfolio").observe(DataEventType.childadded) { (snapshot) in
let key = snapshot.key
let value = snapsnapshot.value as? NSDictionary
//Add everything that is in your firebase
let coinAmount = value?["coinAmount"] as? String
let cointName = value?["coinName"] as? String
//.... Added all the variables for the database
//use the variables above and append the array and it will work!
}
}

Related

Not all elements of my array are being used at the same time (swift)

I have a dynamic array (named "items") of users I follow:
["user5#t.co", " user6#t.co"]
and I'm essentially retrieving these user's posts using:
for i in 0..< items.count{
db.collection("users").document("\(items[i])").collection("posts").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.posts = documents.map { QueryDocumentSnapshot -> Post in
let longitudeVar = data["longitude"] as? String ?? ""
let latitudeVar = data["latitude"] as? String ?? ""
return Post(id: .init(), longitudeVAR: longitudeVAR, latitudeVAR: latitudeVAR)
}
}
}
I'm trying to draw information from both users at the same time but the issue I'm having is that this only draws post information (longitudeVar & latitudeVar) for ONE user OR the other- and it seems to randomly pick between user5#t.co and user6#t.co. Any suggestions? Also I apologize if this is a basic question or if my code isn't well written- I'm just trying to get this to work. Thanks!
If you want to loop database fetches and coordinate their returns into a single task then you should use a DispatchGroup. It's a very simple API that will simply record how many calls you make to Firestore and give you a completion handler to execute when that many returns eventually come in (at the end of the last one).
Don't use a snapshot listener here because those are for streams of data—all you want is a one-time document grab. If there are lots of documents to parse and they are relatively big then I would consider using a background queue so the UI doesn't stutter when this is going on.
var posts = [Post]()
let dispatch = DispatchGroup() // instantiate dispatch group outside loop
for item in items {
dispatch.enter() // enter on each iteration
// make a get-documents request, don't add a continuously-updating snapshot listener
db.collection("users").document(item).collection("posts").getDocuments { (querySnapshot, error) in
if let documents = querySnapshot?.documents {
// fill a local array, don't overwrite the target array with each user
let userPosts = documents.map { QueryDocumentSnapshot -> Post in
let longitudeVar = data["longitude"] as? String ?? ""
let latitudeVar = data["latitude"] as? String ?? ""
return Post(id: .init(), longitudeVAR: longitudeVAR, latitudeVAR: latitudeVAR)
}
self.posts.append(userPosts) // append local array to target array
} else if let error = error {
print(error)
}
dispatch.leave() // always leave, no matter what happened inside the iteration
}
}
/* this is the completion handler of the dispatch group
that is called when all of the enters have equaled all
of the leaves */
dispatch.notify(queue: .main) {
self.tableView.reloadData() // or whatever
}

How do I calculate the sum of the respective variables of a custom type array?

I am new to coding in Swift. My goal is to build a variable containing the sum of a number of custom class variables.
To speak in clear terms:
I have a custom class called "Entry" which has the Double variable "sum", e.g. 50.00.
I am using a ViewController to let the user create a new entry with text field inputs, inter alia a sum of the new entry. This new entry element is then appended to an array of type Entry when the relevant button is pressed.
#IBAction func addEntry(_ sender: UIButton){
// take user input
let name = nameTextField.text ?? ""
let sum = Double(sumTextField.text!) ?? 0.0
let category = selectedCategory
// save user input in new entry
let newEntry = Entry(name: name, sum: sum, category: category)
entries.append(newEntry)
saveEntries()
os_log("Saved new entry successfully.", log: OSLog.default, type: .debug)
In another ViewController, I want to access the array "entries" and build the sum of all sum variables of all Entry elements, e.g. sum of Entry 1 + sum of Entry 2 + sum of Entry 3
My current attempt in coding is as follows:
var entriesDatabase = [Entry]()
//extract sum of entries and build sumOfEntries
var sumArray = [Double]()
for entry in entriesDatabase {
sumArray.append(entry.sum)
}
sumOfEntries = sumArray.reduce(0, +)
The entries array from the first View Controller is saved by using the NSKeyedArchiver and loaded by NSKeyedUnarchiver.unarchiveObject(withFile:) in the second View Controller before calling the function above (which, I am aware, has been deprecated, but it works for my current purposes).
I used the print function to isolate the issue and as far as I can see, the sumOfEntries always remains 0.0, no matter how many Entry elements I create (which itself seems to work, though).
Has anyone a clue what I am doing wrong?
EDIT: The issue to me really seems to be that the calculation does not work rather than the passing of the data from one view to another. Somehow, the arrays always remain empty. The passage of the data works via saving it persistently on the drive and then loading it with the NSKeyedArchiver functions. For clarity see the following code:
/MARK: calculate and display balance
func calculate(){
//load data from user defaults
recurringCalculationValue = UserDefaults.standard.value(forKey: "recurringExpenses") ?? 0.0
monthlyIncomeCalculationValue = UserDefaults.standard.value(forKey: "monthlyIncome") ?? 0.0
//extract sum of entries and build sumOfEntries
var sumArray = [Double]()
for entry in entriesDatabase {
sumArray.append(entry.sum)
}
sumOfEntries = sumArray.reduce(0, +)
//cast the user defaults into Double
let mICV = monthlyIncomeCalculationValue as! Double
let rCV = recurringCalculationValue as! Double
//convert the Strings to Double! and calculate balance
balance = Double(mICV) - Double(rCV) - sumOfEntries
//display balance in sumLabel
sumLabel.text = "\(balance)"
//debugging
print("balance = \(balance)")
print("sumOfEntries = \(sumOfEntries)")
print("monthlyIncomeCalculationValue = \(monthlyIncomeCalculationValue)")
print("recurringCalculationValue = \(recurringCalculationValue)")
print("UserDefault monthlyIncome = \(String(describing: UserDefaults.standard.value(forKey: "monthlyIncome")))")
print("UserDefault recurringExpenses = \(String(describing: UserDefaults.standard.value(forKey: "recurringExpenses")))")
}
//this function is called when the ViewController is opened
#IBAction func unwindToThisViewController(segue: UIStoryboardSegue) {
//Load saved entries into entries array if entries were saved
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
if let pathComponent = url.appendingPathComponent("entries") {
let filePath = pathComponent.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
print("File available.")
entriesDatabase = loadEntries()!
print("Database loaded.")
} else {
print("File not available.")
}
} else {
print("File path not available.")
}
calculate()
}
private func loadEntries() -> [Entry]? {
return NSKeyedUnarchiver.unarchiveObject(withFile: Entry.ArchiveURL.path) as? [Entry]
}
I hope, that makes my problem clearer and thanks again!
The way I see it, in var entriesDatabase = [Entry]() you create a new array for objects of type Entry, which is (of course) initially empty. Therefore, the sum of the values will be 0.
What you want is to cache the values, e.g. in your saveEntries()-Function. You may want to take a look at UserDefaults which stores information in a map-like matter.

How to compare json values in swift

How to compare json array values? Can't understand the logic.
I need to show a price change of cryptocurrencies (increase or decrease) in tableView, while updating data from server https://api.coinmarketcap.com/v1/ticker/
As I understand I have to create two arrays: one for the old values and second for new ones, and function for comparison, where two arrays initially should be equal. What should I do next? Am I in right direction?
var currencies = [Cryptocurrency]()
var newCurrencies = [Cryptocurrency]()
I'm getting the array of data through NSNotificationCenter. Should I change something here?
NotificationCenter.default.addObserver(self, selector: #selector(getCurrencies), name: Notificator.getCurrencyNotification, object: nil)
#objc func getCurrencies(notification: Notification) {
if let receivedCurrencies = notification.object as? [Cryptocurrency] {
self.currencies = receivedCurrencies
AnimatableReload.reload(tableView: tableView, animationDirection: "up")
}
}
func showPriceChanges() {
currencies = newCurrencies
?
}

Add values from multiple dictionaries in firebase into an Int

I'm using firebase to store the amount of views every video in my app has been seen. What I want to is to gather all views from from one users all videos and display the total number of views. However I'm having problems fetching down the data and putting all the dictionary values together into a Int/String!
Ive tried many different solutions so far, but still I get all the different values in like array / values of the dictionary instead of everything added into one value
This is my code for getting all the videoviews of a specific user, no problems with this so far. When I print "intConvert" I get like all the views in different rows.
let ref = Database.database().reference().child("videoviews").child(stringUid)
ref.observe(.value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let numbOfViews = dictionary["views"] as! String
let intConvert = Int(numbOfViews)!
let ArrayViews = [intConvert]
This is my database structure:
**videoviews**
-Lb52VxrEqdSRGljJdP7
views: "25"
-Lb53ARq_lOHEbTruW8s
views: "273"
-Lb53A_cEyX3CYc4mKYn
views: "38"
EDIT: If I do print(dictionary), the dictionary from "if let dictionary = snapshot.value as? [String: Anyobject] looks like this:
["views": 642]
["views": 660]
["views": 628]
["views": 630]
["views": 615]
["views": 3]
["views": 0]
["views": 2]
["views": 1]
Edit: (I was confused and forgot to add the bracelets, sorry for that.)
when I do I "print(dictionary.values) the console looks like this (the key values from different dictionaries):
[642]
[660]
[628]
[630]
[615]
[3]
[0]
[2]
[1]
I then tried to put this together in a loop like this:
var bLoader = 0.0
for hejArray in 0...ArreyViews.count-1{
bLoader += Double(Arrej[hejArray])
}
But when I print "bLoader" I still get all the views for every video in different rows instead of gathered in one value.
So what do I need to do put together all the values from different dictionaries in Firebase into one Variable?
I appreciate all help I can get with this, I can imagine it shouldn't be too hard but that im missing out on something.
EDIT /// I finally found the problem. the "StringUid" that I passed in have different amount of values and therefore the whole function would be called for 9 times if the videos of the user had the amount of 9. The solution that finally worked looked like this:
Global Array declaration:
var myArray = [String]()
Inside the function:
if let dictionary = snapshot.value as? [String: AnyObject]{
let numbOfViews = dictionary["views"] as! String
let intConvert = Int(numbOfViews)!
self.myArray.append(numbOfViews)
let intArray = self.myArray.map { Int($0)!}
let total = intArray.reduce(0, +)
self.totalViewsShower2.text = String(total)
Thank you in advance!
Update
If you can directly print the values from your dictionary like that then the solution might be as easy as
let total = dictionary.values.reduce(0, +)
or if values are strings you need to convert them to Int
let total = dictionary.values.reduce(0) {sum, value -> Int in return sum + (Int(value) ?? 0)}
If on the other hand they values are string but defined as Any you need an extra cast
let total2 = dictionary2.values.reduce(0) {sum, value -> Int in
if let str = value as? String {
return sum + (Int(str) ?? 0)
}
return 0
}
I am not exactly sure what your dictionary contains but I assumed something like this
let dictionary: [String: Any] = ["views": ["1", "2", "3"]]
Then you can cast the value for the "views" key to a String array and then use reduce on that array to get the sum
let array = dictionary["views"] as! [String]
let total = array.reduce(0) {sum, value -> Int in return sum + (Int(value) ?? 0)}
According to your Database structure which looks something like this
{
"videoviews": {
"Lb52VxrEqdSRGljJdP7": {
"views": "25"
},
"Lb53ARq_lOHEbTruW8s": {
"views": "273"
},
"Lb53A_cEyX3CYc4mKYn": {
"views": "38"
}
}
}
let ref = Database.database().reference().child("videoviews").child(stringUid)
ref.observe(.value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let viewsArray = dictionary.values
.compactMap { $0 as? [String: String] }
.compactMap { Int($0["views"] ?? "") }
let totalViews = viewsArray.reduce(0, +)
}
})

filter/identify dictionary keys that have matching values

Not sure if I need reduce, sorting, filtering or using the newer unique methods. I've even tried Equatable solutions.
I need to auto-identify keys that have matching values and take only those keys and put them into a new dictionary or array.
var userDB: [String:Any] = ["userID1":"gameIDa", "userID2":"gameIDa", "userID3":"gameIDc", "userID4":"gameIDd", "userID5":"gameIDe"]
As you can see only these two IDs have the same value of gameIDa. I need output of this result.
// [userID1, userID2]
Thanks
You can use Dictionary(grouping:by:) to achieve this easily and then reduce it to a dictionary which contains only entries with multiple values.
var userDB: [String: String] = ["userID1":"gameIDa", "userID2":"gameIDb", "userID3":"gameIDc", "userID4":"gameIDa", "userID5":"gameIDe"]
let dict = Dictionary(grouping: userDB.keys) { userDB[$0] ?? "" }.reduce(into: [String:[String]](), { (result, element) in
if element.value.count > 1 {
result[element.key] = element.value
}
})
dict
["gameIDa": ["userID1", "userID4"]]
Firstly, in order to be able to compare values, the Dictionary Value type needs to be an Equatable one. Once this is fulfilled, you can easily filter the keys that hold the queried value:
extension Dictionary where Value: Equatable {
func keysWithCommonValues() -> [Key] {
// go over all keys and keep ones for which the dictionary has one another key with the same value
return keys.filter { key in contains { $0.key != key && $0.value == self[key] } }
}
}
// userDB is not inferred as [String:String]
var userDB = ["userID1":"gameIDa", "userID2":"gameIDa", "userID3":"gameIDc", "userID4":"gameIDd", "userID5":"gameIDe"]
print(userDB.keysWithCommonValues()) // ["userID1", "userID2"]

Resources