Accessing Firebase Data inside unique AutoID - swift

This is my first question and I'm still learning Swift/Xcode/Firebase, so I appreciate your patience. I've been stalking StackOverflow and have found a lot of answers to help with various things, but nothing that makes sense for the problem I've been struggling with for 2 days.
I am writing a program that will save a date picked on a previous viewcontroller and a set of user-entered floats from text fields to a Firebase database, and append each data set as a separate entry instead of overwriting the previous data. Using the first block of code below, I've got this problem solved except I can't find a way to do it without using AutoID. This leaves me with a setup like this in Firebase, but with multiple categories and "optionSelected" sections in each category:
program-name
Category 1
optionSelected
L1cggMnqFqaJf1a7UOv
Date: "21-12-2017"
Variable 1 Float: "12345"
Variable 2 Float: "26.51"
L1ciVpLq1yXm5khimQC
Date: "30-12-2017"
Variable 1 Float: "23456"
Variable 2 Float: "35.88"
Code used to save:
func newWithNewVars() {
let myDatabase = Database.database().reference().child("Category 1").child(optionSelected)
let variable1 = textField1.text
let variable2 = textField2.text
let variable1Float = (textField1.text! as NSString).floatValue
let variable2Float = (textField2.text! as NSString).floatValue
let writeArray = ["Date": textPassedOverDate, "Variable 1 Float": variable1Float, "Variable 2 Float": variable2Float]
myDatabase.childByAutoId().setValue(gasArray) {
(error, reference) in
if error != nil {
print(error!)
}
else {
print("Message saved successfully!")
}
}
}
The problem comes with recalling data. Since the AutoID is unique, I can't figure out how to access the data deeper inside for calculations. Specifically, I want to be able to make a new entry, press the save data button, and have it find the most recent entry in the "optionSelected" section so it can do calculations like subtract the older variable 1 from the new variable 1 and such.
Given the above description, layout, and code used above, what code structure would allow me to find the most recent date and access the data inside the AutoID sections for a specific category and "optionSelected"?
Thank you for your help.

The issue you're having is that you're trying to dig deeper but can't as you don't have a hold of that id. You'll want to use the .childAdded in your reference observation when you want to get inside of a list in your JSON tree when you don't have a hold of that id to get inside - this will be called as many times as there are values inside of Category 1 tree:
let reference = Database.database().reference()
reference.child("Category 1").child("optionSelected").observe(.childAdded, with: { (snapshot) in
let uniqueKey = snapshot.key // IF YOU WANT ACCESS TO THAT UNIQUE ID
print(uniqueKey)
guard let dictionary = snapshot.value as? [String: AnyObject] else { return }
let date = dictionary["date"] as? String
let variableOne = dictionary["Variable 1 Float"] as? Float
let variableOne = dictionary["Variable 2 Float"] as? Float
}, withCancel: nil)
You may also want to avoid using spaces in your database keys to avoid any problems in the near future. I'd stick with the common lowercased underscore practice e.g. "category_1" or "variable_2_float"

Related

How to pass and get multiple URLQueryItems in Swift?

Ok, I am working in an iMessage app and am trying to parse more than 1 url query item from the selected message here- I have been successful getting/sending just 1 value in a query:
override func willBecomeActive(with conversation: MSConversation) {
// Called when the extension is about to move from the inactive to active state.
// This will happen when the extension is about to present UI.
if(conversation.selectedMessage?.url != nil) //trying to catch error
{
let components = URLComponents(string: (conversation.selectedMessage?.url?.query?.description)!)
//let val = conversation.selectedMessage?.url?.query?.description
if let queryItems = components?.queryItems {
// process the query items here...
let param1 = queryItems.filter({$0.name == "theirScore"}).first
print("***************=> GOT IT ",param1?.value)
}
}
When I just have 1 value, just by printing conversation.selectedMessage?.url?.query?.description I get an optional with that 1 value, which is good. But with multiple I cant find a clean way to get specific values by key.
What is the correct way to parse a URLQueryItem for given keys for iMessage?
When you do conversation.selectedMessage?.url?.query?.description it simply prints out the contents of the query. If you have multiple items then it would appear something like:
item=Item1&part=Part1&story=Story1
You can parse that one manually by splitting the string on "&" and then splitting the contents of the resulting array on "=" to get the individual key value pairs in to a dictionary. Then, you can directly refer to each value by key to get the specific values, something like this:
var dic = [String:String]()
if let txt = url?.query {
let arr = txt.components(separatedBy:"&")
for item in arr {
let arr2 = item.components(separatedBy:"=")
let key = arr2[0]
let val = arr2[1]
dic[key] = val
}
}
print(dic)
The above gives you an easy way to access the values by key. However, that is a bit more verbose. The way you provided in your code, using a filter on the queryItems array, is the more compact solution :) So you already have the easier/compact solution, but if this approach makes better sense to you personally, you can always go this route ...
Also, if the issue is that you have to write the same filtering code multiple times to get a value from the queryItems array, then you can always have a helper method which takes two parameters, the queryItems array and a String parameter (the key) and returns an optional String value (the value matching the key) along the following lines:
func valueFrom(queryItems:[URLQueryItem], key:String) -> String? {
return queryItems.filter({$0.name == key}).first?.value
}
Then your above code would look like:
if let queryItems = components?.queryItems {
// process the query items here...
let param1 = valueFrom(queryItems:queryItems, key:"item")
print("***************=> GOT IT ", param1)
}
You can use iMessageDataKit library. It makes setting and getting data really easy and straightforward like:
let message: MSMessage = MSMessage()
message.md.set(value: 7, forKey: "user_id")
message.md.set(value: "john", forKey: "username")
message.md.set(values: ["joy", "smile"], forKey: "tags")
print(message.md.integer(forKey: "user_id")!)
print(message.md.string(forKey: "username")!)
print(message.md.values(forKey: "tags")!)
(Disclaimer: I'm the author of iMessageDataKit)

Can you send objects other than strings in URLQueryItems?

Ok, I am building an iMessage app and to transfer data back and forth I have to use URLQueryItems. I am working with an SKScene and need to transfer Ints, CGPoints, images, etc. Reading Apple's documentation and my own attempts it seems like you can only store strings in URLQueryItems.
As this us the only way to pass data back and forth, is there a (better) way to store other types of data? Currently I have been doing this:
func composeMessage(theScene: GameScene) {
let conversation = activeConversation
let session = conversation?.selectedMessage?.session ?? MSSession()
let layout = MSMessageTemplateLayout()
layout.caption = "Hello world!"
let message = MSMessage(session: session)
message.layout = layout
message.summaryText = "Sent Hello World message"
var components = URLComponents()
let queryItem = URLQueryItem(name: "score",value: theScene.score.description)
components.queryItems = [queryItem] //array of queryitems
message.url = components.url!
print("SENT:",message.url?.query)
conversation?.insert(message, completionHandler: nil)
}
Then on the flip side I have to convert this string back to an Int again. Doing this with CGPoints will be inefficient.. how would one pass something like a CGPoint in a URLQueryItem? Any other way than storing the x and y values as strings?
EDIT: This is how I have been receiving data from the other person and putting into their scene:
override func willBecomeActive(with conversation: MSConversation) {
// Called when the extension is about to move from the inactive to active state.
// This will happen when the extension is about to present UI.
// Use this method to configure the extension and restore previously stored state.
let val = conversation.selectedMessage?.url?.query?.description
print("GOT IT ", val)
if(val != nil)
{
scene.testTxt = val!
}
}
As you discovered, to pass data via URLQueryItem, you do have to convert everything to Strings since the information is supposed to be represented as a URL after all :) For CGPoint information, you can break the x and y values apart and send them as two separate Ints converted to String. Or, you can send it as a single String value in the form of "10,5" where 10 is the x and 5 is the y value but at the other end you would need to split the value on a comma first and then convert the resulting values back to Ints, something like this (at the other end):
let arr = cgPointValue.components(separatedBy:",")
let x = Int(arr[0])
let y = Int(arr[1])
For other types of data, you'd have to follow a similar tactic where you convert the values to String in some fashion. For images, if you have the image in your resources, you should be able to get away with passing just the name or an identifying number. For external images, a URL (or part of one if the images all come from the same server) should work. Otherwise, you might have to look at base64 encoding the image data or something if you use URLQueryItem but if you come to that point, you might want to look at what you are trying to achieve and if perhaps there is a better way to do it since large images could result in a lot of data being sent and I'm not sure if iMessage apps even support that. So you might want to look into limitations in the iMessage app data passing as well.
Hope this helps :)
You can use iMessageDataKit library for storing key-value pairs in your MSMessage objects. It makes setting and getting data really easy and straightforward like:
let message: MSMessage = MSMessage()
message.md.set(value: 7, forKey: "moveCount")
message.md.set(value: "john", forKey: "username")
message.md.set(values: [15.2, 70.1], forKey: "startPoint")
message.md.set(values: [20, 20], forKey: "boxSize")
if let moveCount = message.md.integer(forKey: "moveCount") {
print(moveCount)
}
if let username = message.md.string(forKey: "username") {
print(username)
}
if let startPoint = message.md.values(forKey: "startPoint") {
print("x: \(startPoint[0])")
print("y: \(startPoint[1])")
}
if let boxSize = message.md.values(forKey: "boxSize") {
let size = CGSize(width: CGFloat(boxSize[0] as? Float ?? 0),
height: CGFloat(boxSize[1] as? Float ?? 0))
print("box size: \(size)")
}
(Disclaimer: I'm the author of iMessageDataKit)

What is the proper way to get values from NSDictionaries to separate variables

It's not entirely that I can't solve this, but there are proper and improper ways and I don't want to use a flawed method. I have this snapshot from firebase, and I declare it as NSDictionary as so:
let commentDict = commentsDictionary.value as? NSDictionary
When I print commentDict it looks like this:
commentText = Fififgkffy;
postedBy = USXhV0QYkpbSzAmXgfeteXHuoqg2;
commentText = Fhzifigh;
postedBy = USXhV0QYkpbSzAmXgfeteXHuoqg2;
CommentDict will always look like this. It will always have 2 items. I want to know the proper way to grab the items and assign them to variables. One way I have used before, but just on one item is by putting this in a for loop:
if name as! String == "postedBy" {
Then I get the value for the "postedBy" dictionary item. When having 2 items though, should I make a little array for commentText and postedBY, and have 2 arrays with 2 items each? Another important point is that These must stay in their own groups. I have to keep track of which "commentText" was the first group and which was the 2nd group, same with "postedBy".
In the long-run I have to end up with commentText1, postedBy1, commentText2, postedBy2 all as separate String variables.
This is what I ended up doing but I just want to know if there is a better or more "proper" way to do it. I might have to put some safety if statements into this method to make sure the array gets filled properly and all that.
if let commentDict = commentsDictionary.value as? NSDictionary {
for(name, value) in commentDict {
commentInfoArray.append(value as! String)
}
let comment1Text = commentInfoArray[0]
let comment1User = commentInfoArray[1]
let comment2Text = commentInfoArray[2]
let comment2User = commentInfoArray[3]
}
If it's only postedBy and commentText relation that you are looking for try using this as your structure:-
USXhV0QYkpbSzAmXgfeteXHuoqg2 : comment1 : true,
comment2 : true,
//postedById : [postText]
Then at your retrieval use a dictionary to append your data to:-
db_Ref.child(userID).observeSingleEvent(of: .value, with: {(snap) in
if let commentDict = snap.value as? [String:AnyObject]{
for each in commentDict{
print(each.key) // Append your key here
}
}
})

Bypassing code on initial run

In my code I need to retrieve a saved array of data to populate student history data. I am using the following line of code - which works great.
returnedArray = UserDefaults.standard().object(forKey: "studentHistoryArray")! as! NSArray as! [[String]]
The problem I have is on the initial (first) run of the program. The array hasn't been created/saved yet so I need to skip this step, but only on the initial run of the program. Is there a way to run this line of code only on the first run of a program?
var defaults = UserDefaults.standard()
let studentHistoryArrayKey = "studentHistoryArray"
var returnedArray = defaults.object(forKey: studentHistoryArrayKey) as? [[String]]
// I don't think that you need to use the intermediary cast to NSArray, but
// I could be wrong.
if returnedArray == nil {
returnedArray = [[String]]()
defaults.setObject(returnedArray, forKey: studentHistoryArrayKey)
}
// Now it's guaranteed to be non-nil, so do whatever you need to do with the array.
As a rule of thumb, if you're using ! as liberally as in your example, something's going to break.

Retrieving an array from Firebase [duplicate]

This question already has answers here:
How to retrieve objects from firebase by key value
(2 answers)
Closed 6 years ago.
I have an array in Firebase composed of 1's and 0's (true and false basically), they are all stored as separate key/value pairs. I would like to retrieve them from Firebase and append each value to an array in Swift.
I have tried this (found elsewhere on SO) but it is not working.
let ref = Firebase(url:"https://<<unique>>.firebaseio.com/users/\(self.UUID)/scoreArray")
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
if snapshot.value is NSNull {
print("snap is null")
} else {
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? FDataSnapshot {
self.scoreArray.append(rest.value! as! String)
}
}
})
It doesn't crash, it just doesn't fill the array, even though if I print(rest.value) it will give me the array.
So I guess the question is, how do I convert rest.value into a usable form?
EDIT Firebase structure as requested.
66EC8AC4-<<rest of UUID>>
creation_date: "Jun 10, 2016"
extra_quiz_1
q1: "a"
q10: "b"
<<Continues>>
scoreArray
0: "0"
1: "1"
2: "0"
3: "0"
<<continues>>
Working with Array's in Firebase is challenging and in general there are better options to structure data.
In this use case, it may work so here's how it's done.
Given a structure similar to yours:
quiz_0
quiz_name: The Planets
scores
0: Mercury
1: Venus
2: Earth
here's how you would read in the data
let quizzesRef = self.myRootRef.childByAppendingPath("quizzes")
quizzesRef.observeEventType(.Value, withBlock: { snapshot in
for child in snapshot.children {
let quiz_name = child.value["quiz_name"] as! String
print(quiz_name)
let scores = child.value["scores"] as! NSArray
print(scores) //scores is an array
let answer0 = scores[0] //just to demonstrate accessing the array
print(answer0)
}
})
and the output
Planets
(
Mercury,
Venus,
Earth
)
Mercury
That being said, don't use arrays. Here's a suggestion that may be a far more flexible. Renumbering questions is a snap, modifying the question or answer is easy as well. The -Asidijaid keys are generated using childByAutoId - helps to disassociate dynamic data from static keys.
quiz_0
quiz_name: The planets
quiz_data
-Asok99idkasdsl
question_number: 0
question: Which planet closet to the Sun?
answer: Mercury
-Yklaosokjdinoisd
question_number: 1
question: Which rocky planet is hotter than Mercury
answer: Venus
-Klkooksow999sdd
question_number: 2
question: What is the third planet from the Sun
answer: Earth