Swift Firebase Database get data - swift

I have Firebase cloud structure:
How can I catch name of firstBook and secondBook and write to the array
And create another array with quotes(Quote1+ Qoute2+ Quote3) from firstbook
I'm try this :
var array: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
Database.database().reference().ref?.child("category").child("book").observe(.value, with: { (snapshot) in
let valueCat = snapshot.value as! NSDictionary
let username = valueCat["name"] as? String ?? ""
print(username)
self.array.append(username)
}
}
To another array :
var quotAarray: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
Database.database().reference().ref?.child("category").child("book").child("firstBook").observe(.value, with: { (snapshot) in
let valueCat = snapshot.value as! NSDictionary
let quote = valueCat["Quotes"] as? String ?? ""
print(quote)
self.quotAarray.append(quote)
}
}

Check if this helps you I made a sample Dictionary as per your response received
Code used:
/// Sample Data on Base of your Output SHown
var DBData = [["firstBook":["Quotes": ["Quote1":111, "Quote2":222, "Quote3":333 ], "name":"first_Book"]], ["SecondBook":["Quotes":["Quote1":111,"Quote2":222,"Quote3":333],"name":"Second_Book"]]]
/// Names Array - In which book names need to be added
var nameArray = [String]()
/// For loop on base of DBData as it Array of Array Data
for i in 0..<DBData.count {
/// Assumed You have only one Main key in each array
/// Can be known before .
/// if you do not know Let's get keys here in Array at index i
let keysArray : [String] = Array(DBData[i].keys)
/// Now get the current dict value on base of key retrieved as in
/// first case its - firstBook
/// Second case its - SecondBook
let currentDictValue = DBData[i][keysArray[0]]
/// Now get Names of books
/// name is the key used in your DB its generic and its fixed
/// Now get value and add in Array
nameArray.append(currentDictValue!["name"] as! String)
}
/// Output
print("Name Array:\(nameArray)")
Final output:
Note use either Quotes or Quote , Keys should be similar in DB

Try this :
let valueCat = snapshot.value as! NSDictionary
if let firstBook = valueCat["firstBook"] as? NSDictionary {
print(firstBook["name"])
}
To get all book names you have to iterate through the childs

Related

Filter an (Codable) array by another array

I'm trying to filter my json data by IDs (trying mark some favourites and filter using it)
struct workoutList : Codable {
let id : Int
let title : String
let tag : String
}
func selectedWorkoutGroup(libraryFilter: Int, jsonErgWorkouts:[workoutList], workoutGroupBox: UITextField) -> [workoutList] {
var selectedGroup = [workoutList]()
let workoutFav = [1,10,100]
if libraryFilter == 0 {
// This works because I'm filtering based on 1 specific item
selectedGroup = jsonErgWorkouts.filter { $0.tag == workoutGroupBox.text }
} else if libraryFilter == 1 {
// Here I want to filter and show only the favorites
selectedGroup = jsonErgWorkouts.filter { $0.id } //
print("selectedGroup:\(selectedGroup)")
}
return selectedGroup
}
in the above code, the filter works when I have 1(one) something specific item to filter and then I get the entire json array with that tag.
Now I want to implement a favorite list, where the user selects for example ID == [1, 10 ,100] as their favourite.
How can I use the filter command to do it? I tried a few things and searched through SO (but doesn't work). Most of the answers are based on filtering based on specific items eg:
selectedGroup = jsonErgWorkouts.filter { workoutFav?.contains($0.id) }
edit: (omitted that I am using/storing the favourites in userDefaults. This code gives the error of "type of expression is ambiguous without more context"
func selectedWorkoutGroup(libraryFilter: Int, jsonErgWorkouts:[workoutList], workoutGroupBox: UITextField) -> [workoutList] {
var selectedGroup = [workoutList]()
UserDefaults.standard.set([1,10,100], forKey: "workoutFavorite")
/// This one gets stored as [Any] so I cast it to [Int]
let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int]
if libraryFilter == 0 {
// This works because I'm filtering based on 1 specific item
selectedGroup = jsonErgWorkouts.filter { $0.tag == workoutGroupBox.text }
} else if libraryFilter == 1 {
selectedGroup = workoutFav.flatMap { favouriteId in // for each favourite ID
jsonErgWorkouts.filter { $0.id == favouriteId } // This returns Error "type of expression is ambiguous without more context"
} // flatMap joins all those arrays returns by "filter" together, no need to do anything else
print("selectedGroup:\(selectedGroup)")
}
return selectedGroup
}
Final Solution:
Changing from This
let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int]
to This (notice the as! instead of as?)
let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as! [Int]
works using #sweeper's answer. Thanks
Update:
Figured out why this error occurred "type of expression is ambiguous without more context" when casting the output of UserDefaults as? [Int] and had to use as! [Int]
But using as! [Int] force unwrapping it causes app to crash if the user did not have any favorites saved into the UserDefault. (Which I then had to code around) like below
var workoutFav = [Int]()
if !(UserDefaults.standard.array(forKey: "workoutFavorite") == nil) {
workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as! [Int]
}
Which was then simplified and removed the force unwrapping based on this SO https://stackoverflow.com/a/37357869/14414215 to become this one-line
let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int] ?? [Int]()
You need to do that filter for each id in the favourites array. You get an array of arrays as a result. To get the final array, you need to join those arrays to a single array. This "map each thing to an array and join the arrays" operation is what a flatMap does:
workoutFav.flatMap { favouriteId in // for each favourite ID
jsonErgWorkouts.filter { $0.id == favouriteId } // find workouts that match the ID
} // flatMap joins all those arrays returns by "filter" together, no need to do anything else
First thing first please give a struct name with a capital so you can distinguish between instance of it. Second you need to have new array where you will store each favorite, and store permanently that array, core data or some base on server, form there you will fetch favorites.
The better way is to add property like isFavorite: Bool that is false by default, and if user change it you can set it to be true, in that way you can avoid using ids for that and you can store whole workout's in one array to core data or base that you use, after that you can fetch from there with
let favorites = workouts.compactMap { $0.isFavorite == true }
Here you go in that way, but just to mention it highly recommended that you store those type of data outside User defaults.
struct Fav {
let name: String
let id: String
}
let df = UserDefaults.standard
let jk = ["aaa", "bbb", "cccc"]
df.setValue(jk, forKey: "favorites")
let fav1 = Fav(name: "zzz", id: "aaa")
let fav2 = Fav(name: "bbb", id: "qqq")
let favs = [fav1, fav2]
let favIDs = df.value(forKey: "favorites") as? [String]
favIDs?.forEach({ (id) in
let f = favs.filter({$0.id == id}) // here it is
})

Swift Firebase - Convert database snapshot into an array

I have a groups reference in firebase that looks like this:
I'm having trouble converting the list of members into an array of strings in my app.
I'm fetching the data like so:
//Reference to each group
let ref = Database.database().reference().child("groups").child(snapshot.key)
//Get the group data from the reference
ref.observeSingleEvent(of: .value, with: { (groupSnap) in
//Cast data as dictionary [String:Any]
if let dictionary = groupSnap.value as? [String: Any] {
//Parse each group object
if let group = Group.parse(snapshot.key, dictionary) {
groups.insert(group, at: 0)
}
//Escape with group array
complete(groups)
}
})
And currently parsing the data without the members:
static func parse(_ key: String, _ data: [String:Any]) -> Group? {
let name = data["name"] as! String
let category = data["category"] as! String
let owner = data["owner"] as! String
return Group(id: key, name: name, category: Group.Category(rawValue: category)!, ownerId: owner, members: nil)
}
How would I turn the members list into an array of strings for my group object?
// example data
let data = [
// "name": ...
// "category": ...
// "owner": ...
"members": [
"member1": true,
"member2": false,
"member3": true,
"member4": true
]
]
// grabbing the members element like you do in your parse function
let members = data["members"] as! [String: Bool]
let membersAsListOfStrings = Array(members.keys)
print(membersAsListOfStrings) // -> ["member4", "member1", "member3", "member2"]
let filteredMembersAsListOfStrings = Array(members.filter { $0.value }.keys)
print(filteredMembersAsListOfStrings) // -> ["member4", "member3", "member1"]
You're looking for the .keys attribute. I believe all dictionaries in Swift have this. This code ran for me fine in a playground.

Firebase-Swift How to sum a child values together

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.

How to create nested dictionary elements in Swift?

I want to create a variable which stores this:
["messageCode": API_200, "data": {
activities = (
{
action = 1;
state = 1;
}
);
messages = (
{
body = hi;
// ...
}
);
}, "message": ]
What I have done is this:
var fullDict: Dictionary<String, AnyObject> = [:]
fullDict["messageCode"] = "API_200" as AnyObject
var data: Dictionary<String, AnyObject> = [:]
fullDict ["data"] = data as AnyObject
Is this way is correct and how I can add activities?
I would suggest to go with creating a custom Model:
struct Model {
var messageCode: String
var data: MyData
var message: String
}
struct MyData {
let activities: [Activity]
let messages: [Message]
}
struct Activity {
var action: Int
var state: Int
}
struct Message {
var body: String
// ...
}
Thus you could use it as:
let data = MyData(activities: [Activity(action: 1, state: 1)], messages: [Message(body: "hi")])
let myModel = Model(messageCode: "API_200", data: data, message: "")
However, if you -for some reason- have to declare it as a dictionary, it could be something like this:
let myDict: [String: Any] = [
"messageCode": "API_200",
"data": ["activities": [["action": 1, "state": 1]],
"messages": [["body": "hi"]]
],
"message": ""
]
which means that myDict is a dictionary contains:
messageCode string.
data as nested dictionary, which contains:
activities array of dictionaries (array of [String: Int]).
messages array of dictionaries (array of [String: String]).
message string.
One of the simplest reasons why you should go with the modeling approach is because when it comes to read from myModel, all you have to do is to use the dot . notation. Unlike working with it as a dictionary, you would have to case its values which could be a headache for some point. For instance, let's say that we want to access the first message body in data messages array:
Model:
myModel.data.messages.first?.body
Dictionary:
if let data = myDict["data"] as? [String: [[String: Any]]],
let messages = data["messages"] as? [[String: String]],
let body = messages.first?["body"] {
print(body)
}
Since you explicitly want it as [String:AnyObject]:
var dict: [String:AnyObject] = ["messageCode":"API_200" as AnyObject,
"data": ["activities": [["action":1,
"state":1]],
"messages": [["body":"hi"]]] as AnyObject,
"message": "" as AnyObject]
Basically all the root values should be typecasted as AnyObject
Or the long way:
//Activities is as Array of dictionary with Int values
var activities = [[String:Int]]()
activities.append(["action": 1,
"state": 1])
//Messages is an Array of string
var messages = [[String:String]]()
messages.append(["body" : "hi"])
//Data is dictionary containing activities and messages
var data = [String:Any]()
data["activities"] = activities
data["messages"] = messages
//Finally your base dictionary
var dict = [String:AnyObject]()
dict["messageCode"] = "API_200" as AnyObject
dict["data"] = data as AnyObject
dict["message"] = "" as AnyObject
print(dict)
Parsing this to get your data back will be hell; with all the type casts and all.
Example (lets capture action):
let action = ((dict["data"] as? [String:Any])?["activities"] as? [String:Int])?.first?.value
As you can see you need to typecast at every level. This is the problem with using dictionaries in Swift. Too much cruft.
Sure, you could use a third-party library like SwiftyJSON to reduce the above to:
let action = dict["data"]["activities"][0]["action"]
But do you want a dependency just for something as simple as this?
Instead...
If your structure is defined then create models instead; as Ahmad F's answer suggests. It will be more readable, maintainable and flexible.
...but since you asked, this is how one would do it with pure Dictionary elements.

'AnyObject?' does not have a member named 'count'

I'm trying to loop through an (as I would interpret) array by arrayname.count like:
if var storedToDoItems: AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey("toDoItems") {
toDoItems = []
for var i = 0; storedToDoItems.count; i++ {
toDoItems.append(storedToDoItems[i] as NSString)
}
}
and receive the error in the title marked by storedToDoItems.count. I'm populating first storedToDoItems like this:
...
toDoItems.append(tb_toDoItem.text)
let fixedToDoItems = toDoItems
// Store items
NSUserDefaults.standardUserDefaults().setObject(fixedToDoItems, forKey: "toDoItems")
// Save the stored stuff
NSUserDefaults.standardUserDefaults().synchronize()
...
If you store an array to the user defaults, you should try to cast it to an array when you extract it.
If toDoItems is defined as an array of strings [String], then you just have to use optional binding combined with optional cast, and just copy the extracted array to toDoItems:
if let storedToDoItems: [String] = NSUserDefaults.standardUserDefaults().objectForKey("toDoItems") as? [String] {
toDoItems = storedToDoItems
}
Being an array a struct, i.e. a value type, it is assigned by copy, so when assign storedToDoItems to toDoItems, a copy of it is created and assigned - so you don't need to manually add each element individually.
Your code instead has 2 errors:
you have defined storedToDoItems as an optional AnyObject, and optionals don't have a count property.
even if it is not defined as optional, AnyObject doesn't have a count property as well. It's irrelevant that the actual type stored in the variable is an array, the variable is declared as AnyObject and that is how is treated by the compiler.
You need to tell what's inside:
toDoItems = [String]()
if let storedToDoItems = NSUserDefaults.standardUserDefaults().objectForKey("toDoItems") as? [String] {
for item in storedToDoItems {
toDoItems.append(item)
}
}
and even shorter:
toDoItems = [String]()
if let storedToDoItems = NSUserDefaults.standardUserDefaults().objectForKey("toDoItems") as? [String] {
toDoItems = storedToDoItems
}
I just used this Bold line will work for you hopefully
if var toDoStored: [String] = NSUserDefaults.standardUserDefaults().objectForKey("toDoSaved") as? [String]{
tblItems = []
for var i = 0; i < toDoStored.count ; ++i {
tblItems.append(toDoStored[i] as NSString)
}
}