Swift Firebase - Convert database snapshot into an array - swift

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.

Related

Swift Firebase Database get data

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

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.

How to access nested dictionary?

I can print this in the debugger:
(lldb) print params["message"]!
([String : String]) $R5 = 2 key/value pairs {
[0] = (key = "body", value = "iPadUser has started a new stream")
[1] = (key = "title", value = "Stream started")
}
But I am trying to figure out how to access the body and title separately.
I construct params in this way:
let recipients = ["custom_ids":[recips]]
let notificationDetails = "hello there"
let content = [
"title":title,
"body":details
]
let params: [String:Any] = [
"group_id":"stream_requested",
"recipients": recipients,
"message": content
]
print((params["message"] as! [String: Any])["title"] as! String)
You need to cast the Dictionary value as specific type, since the compiler doesn't know what to expect. (Please mind that you mustn't use force unwrap in other way than example code.)
Considering you need to fetch array values when recipients dictionary looks like this:
let recipients = ["custom_ids":["recipe1", "recipe2", "etc"]]
get to the ids like this:
guard let recipients = params["recipients"] as? [String: Any],
let customIDs = recipients["custom_ids"] as? [String]
else { return }
for id in customIDs {
print(id) // Gets your String value
}

Generate section headers from dictionary [Swift 3]

I have an array of dictionaries with the following type of structure (which is already sorted) :
[
[
"id": 1,
"name": "ItemA",
"url": "http://url.com"
],
[
"id": 32,
"name": "ItemB",
"url": "http://url.com"
],
...
]
Declared as an array of dictionaries for AnyObject :
var arrayApps = [[String:AnyObject]]()
This array of dictionaries is generated using SwiftyJson :
[..]
if let resData = swiftyJsonVar["data"].arrayObject {
self.arrayItems = resData as! [[String:AnyObject]]
}
[..]
My Goal is to display those items in sections by using the sections headers but after trying to figure it out and looking for an answer, i'm unable to move on.
I've tried to groupe the dictionaries by letters to get a result like this:
[
"A":{[foo1],[foo2]},
"D":{[foo3],[foo5]},
"F":{[foo4],[foo6]}
...
]
But no luck, i've always ended up with errors because my array contains "Optionals".
In summary :
How can I generate Alphabetical section headers based on the name inside a TableView using an array of dictionaries not grouped like the one given above in Swift 3 ?
Thank you in advance !!
You can use the .sorted(by: ) method of Array to compare to elements of you array with each other.
This yields a sortedArray:
let sortedArray = arrayOfApps.sorted(by: {($0["name"] as! String) <= ($1["name"] as! String)})
This will crash if the itemName is not a String but I left it to you to handle any errors. For example changing it to:
$0["name"] as? String ?? ""
EDIT:
// Removed examples and added extension to create desired result
I found one of my old projects where I wrote such extension. Changed it a bit to suit your needs, tell me if it needs some change still:
extension Array {
func sectionTitlesForArray(withName name: (Element) -> String) -> Array<(title: String, elements: NSMutableArray)> {
var sectionTitles = Array<(title: String, elements: NSMutableArray)>()
self.forEach({ element in
var appended = false
sectionTitles.forEach({ title, elements in
if title == name(element) {
elements.add(element)
appended = true
}
})
if appended == false {
sectionTitles.append((title: name(element), elements: [element]))
}
})
return sectionTitles
}
}
// Usage single letter as Section title:
let sectionTitles = arrayOfApps.sectionTitlesForArray(withName: {
let name = $0["name"] as! String
return String(name[name.startIndex])
})
// Quick dirty pretty-print:
sectionTitles.forEach({ sectionTitle in
print("Section title: \(sectionTitle.title) \n")
sectionTitle.elements.forEach({ object in
let element = object as! Dictionary<String,Any>
print("Element name: \(element["name"]!)")
})
print("")
})

Dictionary in swift

let json: [AnyObject] = {
"response": "get_nearby_deals",
"userID": "12345",
"demo":[{"deal_code":"iD1612061"}]
}
How to declare Dictionary in Swift? I'm new in Swift. Totally stuck.
You have declared Array using [AnyObject], just change it to [String: Any] and replace curly braces {} with square brackets [].
let json: [String: Any] = [
"response": "get_nearby_deals",
"userID": "12345",
"demo":[["deal_code":"iD1612061"]]
]
And you can retrieve value from Dictionary using subscript like this.
let userID = json["userID"] as! String
//Above will crash if `userID` key is not exist or value is not string, so you can use optional wrapping with it too.
if let userID = json["userID"] as? String {
print(userID)
}
//`demo` value is an array of [String:String] dictionary(s)
let demo = json["demo"] as! [[String:String]]
//Same here as the previous `userID`, it will crash if 'demo' key is not exist, so batter if you optionally wrapped it:
if let demo = json["demo"] as? [[String:String]] {
print(demo)
}