Complex Firebase Query with iOS Swift - ios

My DB looks like this:
shows{
show1{
name: //Showname
start: //Timestamp start
end: //Timestamp end
rating: //Showrating INT
}
show2{
...
}
}
How can i query the shows, which are running now (start < now && end > now), ordered by the rating?
Is this even possible with this Database Structure or do i have to change it?

You should name shows' children nodes by their UID, not "show1", "show2", etc. Then you would query your database for the shows ordered by their rating, and use a conditional to test whether each result is within the desired time frame. I haven't actually tested this code, but something like this should work:
ref?.child("shows").child(getUid()).queryOrdered(byChild: "rating").observeEventType(.Value, withBlock: { snapshot in
for child in snapshot.children as? [String: AnyObject] {
// filter results
if (child["start"] <= currentTime && child["end"] >> currentTime ) {
// results
resultsArray.append(child)
}
}
However, I recommend reading about denormalizing data in Firebase first:
https://firebase.google.com/docs/database/ios/structure-data
https://stackoverflow.com/a/16651115/3502608
And read the docs over querying after you understand denormalization:
https://firebase.google.com/docs/database/ios/lists-of-data

First of all if you are using timestamps and you want to manipulate them in your front end or perform any algorithmic procedure over the timestamp (i.e > or <) then use NSDate not FIRServerValue.timestamp().
To query your show that are having the end : before the current timestamp try using this:-
let currentTimeStamp = Int(NSDate.timeIntervalSinceReferenceDate*1000)
FIRDatabase.database().reference().child("shows").queryOrdered(byChild: "end").queryStarting(atValue: currentTimeStamp).observeSingleEvent(of: .value, with: {(Snapshot) in
print(Snapshot)
})
This will give you all the shows who are running now. Also for this to work you have to store the value of start and end in similar fashion i.e Int(NSDate.timeIntervalSinceReferenceDate*1000)
To order them according to your show rating , you can only retrieve the values and store them in a struct.
struct show_Struct {
var name : String!
var rating : Int! //If it is int or float if it is of type float.
...
}
Before calling the reloadData() function on any of your tableView or collectionView, just call
let showFeed = [show_Struct]()
..
self.showFeed.sort(by: {$0.rating > $1.rating})
self.tableView.reloadData()

Related

Compare different rows and make them one at tableview - Firebase/Swift 3

I am new in Swift 3 and Firebase programming, but I am making good progress. But sometimes things come harder and here I am again because I could not find some text as specifically as I need, neither updated for Swift 3. I have tried many many ways.
The issue is that I am building an app for some teachers to make a kind of examination with their students. Each Student has the same examination by two different teachers at different times and the results are shown at a table view controller like the following image:
As you can see, I have two rows for each student with two different scores (partial). What I wish is to filter this Firebase data, search for those two “repeated” students and make an average score from them and show the data at a table view controller (exam1 + exam2 / 2) for the final average score. This is my currently Firebase structure (I will post an alternative later at this question):
Now, parts of the code:
1. Struct for variables and snapshot
import Foundation
import Firebase
struct Examination {
var nome: String?
var preceptor: String?
var dataHoje: String?
var nota: String?
var key: String?
init(nome: String, preceptor: String, nota: String, dataHoje: String, key: String) {
self.nome = nome
self.preceptor = preceptor
self.nota = nota
self.dataHoje = dataHoje
self.key = key
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as? [String: AnyObject]
nome = snapshotValue?["nome"] as? String
preceptor = snapshotValue?["preceptor"] as? String
nota = snapshotValue?["nota"] as? String
dataHoje = snapshotValue?["dataHoje"] as? String
notaAtitude = snapshotValue?["notaAtitude"] as? String
}
func toAnyObject() -> Any {
return [
"nome": nome,
"preceptor": preceptor,
"nota": nota,
"dataHoje": dataHoje,
"userKey": key,
]
}
}
Load method for main table view controller
import Foundation
import Firebase
var refAlunos: [Examination] = []
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
Load_HM1()
}
func Load_HM1() {
let refW = FIRDatabase.database().reference().child("semestre_1_2017").child("avaliacaoHM12017").child("cheklists")
refW.queryOrdered(byChild: "nome").observe(.value, with: {(snapshot) in
var newTeste2: [Examination] = []
for resposta in snapshot.children {
let itemsAadicionar = Examination(snapshot: resposta as! FIRDataSnapshot)
newTeste2.append(itemsAadicionar)
}
self.refAlunos = newTeste2
})
}
At last, the Firebase structure I have also tried, but I always receive nil as result, table view empty:
The code for this alternative way (method load), I could not implement:
func Load_HM1() {
let refW = FIRDatabase.database().reference().child("semestre_1_2017").child("avaliacaoHM12017").child("cheklists") ... I CRASHED HERE..
// The rest of this method is the same shown before
So, resuming the issue, I need a way to compare rows with the same student name (always will be 2) and calculate the average of these two scores and publish a table view with this final score and name, almost as the first image, but only changing the score to the final average. I really don’t know if there is a way and I don’t know which way to go. I have searched a lot, but nothing found. I guess this second firebase structure could be more friendly, but my rows are empty. If need more information to understand this issue, ask me that I update the question. Thank you very much for any help.
I'm not aware of Firebase providing such functionalities out of the box, therefore I would recommend doing your calculations manually.
Before setting self.refAlunos at the end of the callback function you can extract the corresponding pairs of examinations, compute the average grade for each pair and create a new instance containing the computed average grade and the remaining attributes. Afterwards you can store an array of the generated examinations in self.refAlunos.
Additionally, you should take a look at Structure Your Database and Structuring your firebase data. Applying these guidelines will help to flatten your data and improve performance as your data grows.
Edit:
I'm currently not on my Mac, but I hope my suggestions can point you in the right direction, although they might not be 100% syntactically correct.
func Load_HM1() {
let refW = FIRDatabase.database().reference().child("semestre_1_2017").child("avaliacaoHM12017").child("cheklists")
refW.queryOrdered(byChild: "nome").observe(.value, with: {(snapshot) in
var newTeste2: [Examination] = []
var examinations: [Examination] = []
var position = -1
for resposta in snapshot.children {
let itemsAadicionar = Examination(snapshot: resposta as! FIRDataSnapshot)
newTeste2.append(itemsAadicionar)
for exam in 0..<examinations.count {
if (examinations[exam].nome == itemsAadicionar.nome) {
position = exam
break
}
}
if (position != -1) {
examinations[position].nota += itemsAadicionar.nota
examinations[position].nota /= 2
position = -1
}
}
self.refAlunos = examinations
})
}
I have edited your code and I hope you will get the gist. However, this approach is only suitable if you always have two exams per student. If you have more, you need to adapt it a little bit.

Storing AnyObject in array?

I've got an array that I'm trying to store timestamps into.
I'm testing stuff out with something like:
#IBAction func buttonPressed(sender:UIButton!) {
let post = ["MinutesLeft" : (FIRServerValue.timestamp())]
DataService.ds.REF_POSTS.childByAutoId().setValue(post)
}
Then I'm trying to call it back and fill an array with some of those timestamps:
DataService.ds.REF_POSTS.queryOrderedByChild("MinutesLeft").queryStartingAtValue(Int(cutoff)).observeEventType(.ChildAdded, withBlock: { (snapshot:FIRDataSnapshot) in
let post = snapshot.value!["MinutesLeft"]
print("POST \(post)" )
self.postsArray.append(post)
print(self.postsArray)
} else {
print("Didn't work")
}
})
Then I'm running a timer that's meant to clear out certain posts:
func removeExpiredItems() {
let cutoff = UInt64(1000 * floor(NSDate().timeIntervalSince1970) - 10*60*1000)
while (self.postsArray.count > 0 && self.postsArray[0] < cutoff) {
// remove first item, because it expired
self.postsArray.removeAtIndex(0)
print(postsArray.count)
}
}
My issues are :
I don't know what kind of array to have here. It's telling me that a timestamp is an AnyObject, but when I go to make an array of anyobjects I get a straight up Xcode internal error crash...so I guess that's a no-go.
I can translate it all to strings and just store it like that, but the issue comes then when I'm trying to compare the times vs my cutoff in my removeExpiredItems func.
I was trying to do something like:
Take the timestamps, change them to a string , then down when I'm going to make the comparison change the String to an Int but I get something like "This kind of conversion will always fail".
Any ideas here?
let post = snapshot.value!["MinutesLeft"]
The type of post will be AnyObject, (Optional<AnyObject>) in this case.
You have store it as the data type you want. For example in this case you can store it as NSTimeInterval
let post = snapshot.value!["MinutesLeft"] as! NSTimeInterval
And store it in an Array of NSTimeInterval as
var postsArray :[NSTimeInterval] = []

firebase queryendingatValue("") always returns first child in database

I am trying to do a keyword search using firebase queries but they do not seem to ever retrieve the correct value. Its always the first child of the database. the database is organized using child by autoid. my data looks like this
posts :
-KJj6DMQVcaOIBZ76X03
category:
"cleaner"
dishname:
"Lysol"
likes
8QCQPfShSTdYe1VbCxHK4dkJPFj1:
true
likesCount:
1
picURL:
"https://firebasestorage.googleapis.com/v0/b/dis..."
poster:
"8QCQPfShSTdYe1VbCxHK4dkJPFj1"
price:
"$5"
restaurant:
"house"
-KJj6PaHt9EfXQ5-EU-m
-KJnTUcb3BvZDMI0Pxgo
-KJnTl4giuy5QMvdeEo5
the function that is doing the search looks like this
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
filteredPosts.removeAll()
let postref = firebase.child("posts")
let search = searchController.searchBar.text!
print(search)
let postQ = (postref.queryOrderedByKey().queryEndingAtValue(search))
// postQ.keepSynced(true)
postQ.observeSingleEventOfType(.ChildAdded, withBlock: { (snapshot) in
self.filteredPosts.append(snapshot)
print(snapshot)
self.shouldShowSearchResults = 2
self.tableView.reloadData()
})
searchController.searchBar.resignFirstResponder()
}
It is super frustrating. please help
You are ordering by key, postref.queryOrderedByKey(), and then querying by that key's value queryEndingAtValue(search).
So unless your search variables is one of the childByAutoId values (-KJnTUcb3BvZDMI0Pxgo), I don't think you'll get back what you want.
Instead order by the child property you are searching against. Let's say you want to search by a post's category.
var search = "cleaner"
let categoryQuery = postref
.queryOrderedByChild("category")
.queryEndingAtValue(search)
This would pull back the record of -KJj6DMQVcaOIBZ76X03.

In Firebase, how can I query the most recent 10 child nodes?

I'm using childByAutoId() to generate my children. Each child looks like:
{
user_id: 1
}
I'd like to get the last 10 most recently added, sorted by time DESC. What's the easiest way to do this?
The answer is that you need to use a bit of reverse logic, and also store a timestamp key:value pair within each node as a negative value. I omitted the user_id: 1 to keep the answer cleaner.
Here's the Firebase structure
"test" : {
"-KFUR91fso4dEKnm3RIF" : {
"timestamp" : -1.46081635550362E12
},
"-KFUR9YH5QSCTRWEzZLr" : {
"timestamp" : -1.460816357590991E12
},
"-KFURA4H60DbQ1MbrFC1" : {
"timestamp" : -1.460816359767055E12
},
"-KFURAh15i-sWD47RFka" : {
"timestamp" : -1.460816362311195E12
},
"-KFURBHuE7Z5ZvkY9mlS" : {
"timestamp" : -1.460816364735218E12
}
}
and here's how that's written out to Firebase; I just used a IBAction for a button to write out a few nodes:
let testRef = self.myRootRef.childByAppendingPath("test")
let keyRef = testRef.childByAutoId()
let nodeRef = keyRef.childByAppendingPath("timestamp")
let t1 = Timestamp
nodeRef.setValue( 0 - t1) //note the negative value
and the code to read it in
let ref = self.myRootRef.childByAppendingPath("test")
ref.queryOrderedByChild("timestamp").queryLimitedToFirst(3).observeEventType(.ChildAdded, withBlock: { snapshot in
print("The key: \(snapshot.key)") //the key
})
and I declared a little function to return the current Timestamp
var Timestamp: NSTimeInterval {
return NSDate().timeIntervalSince1970 * 1000
}
and the output
The key: -KFURBHuE7Z5ZvkY9mlS
The key: -KFURAh15i-sWD47RFka
The key: -KFURA4H60DbQ1MbrFC1
As you can see, they are in reverse order.
Things to note:
Writing out your timestamp as negative values
When reading in use .queryLimitedToFirst instead of last.
On that note, you can also just read the data as usual and add it to an Array then then sort the array descending. That puts more effort on the client and if you have 10,000 nodes may not be a good solution.
I'm assuming your data actually looks like this:
someDataSet: {
longUID-1: {
timeCreated: 9999999999, // (seconds since the javascript epoch)
user_id: 1
},
longUID-2: {
timeCreated: 1111111111,
user_id: 2
},
longUID-3: {
timeCreated: 3141592653,
user_id: 3
}
}
You could automate that by calling Firebase.push({user_id: ###, timeCreated: ###}) multiple times in a for loop or any other method. Maybe you're adding news stories to a webpage, but you only want your user to see the most current stories--- IDK. But the answer to your question is to use Firebase's ref.orderByChild() and ref.limitToLast().
var ref = new Firebase("<YOUR-FIREBASE-URL>.firebaseio.com/someDataSet");
//the "/someDataSet" comes from the arbitrary name that I used up above
var sortedRef = ref.orderByChild('timeCreated');
//sort them by timeCreated, ascending
sortedRef.limitToLast(2).on("child_added", function(snapshot){
var data = snapshot.val();
console.log(data);
/* do something else with the data */
});
//The console would look like this
// Object {timeCreated: 9999999999, user_id: 1}
// Object {timeCreated: 3141592653, user_id: 3}
This happened because the program took the child with the greatest timeCreated value first and then the second greatest (value) second...
Also note, the longUID means nothing when you sort them by child and neither do the other values (user_id in this case)
Here is the documentation for:
Firebase .push() method (Sorry, I'm not allowed to post this link- I dont have enough reputation)
Firebase .orderByChild method
And also, Firebase .limitToLast method
The code: ref.queryOrderedByKey().queryLimitedToLast(10) can be used for getting the most recent 10 data. However, this is an ascending order by default.
Alternatively, you can order your data via
ref.orderByChild("id").on("child_added", function(snapshot) {
console.log(snapshot.key());
});
This also presents an ascending order by default. To change it into descending order is little bit tricky. What I would suggest it to multiply ids by -1 as shown below and then sort them.
var ref= new Firebase("your data");
ref.once("value", function(allDataSnapshot) {
allDataSnapshot.forEach(function(dataSnapshot) {
var updatedkey = -1 * dataSnapshot.key();
ref.update({ element: { id: updatedkey}});
});
});
This two SO page might be useful for you also, please check:
How to delete all but most recent X children in a Firebase node?
firebaseArray descending order?

Get the object with the most recent date

I have an array of objects of type Thing:
class Thing: NSObject {
var data: String
var type: String
var created: NSDate
}
These things have an NSDate property called created. My aim is to write a function that reads the created property of every thing in the array and returns the thing that has the most recent date. The function looks like this:
public func getLastSwipe(list: Array<Thing>) -> Thing {
return someThing
}
Another approach is using Swift's .max, like this:
dates.max(by: <)
The following is my old answer. The above is updated in feb 2023.
let mostRecentDate = dates.max(by: {
$0.timeIntervalSinceReferenceDate < $1.timeIntervalSinceReferenceDate
})
This is the most performant solution I've found.
Returns the sequence’s most recent date if the sequence is not empty; otherwise, nil.
You could use reduce if you wanted. This will find the object with the highest timestamp.
var mostRecent = list.reduce(list[0], { $0.created.timeIntervalSince1970 > $1.created.timeIntervalSince1970 ? $0 : $1 } )
If your dates are not all in the past, you'll have to also compare against the current date to determine a cutoff. If your dates are all in the future, you'll want to switch the > to < to find the next future date (lowest timestamp).
You can sort the array, then find the first/last element. For example...
let objects: [Thing] = ... //Set the array
let mostResent = array.sorted { (firstThing, secondThing) -> Bool in
firstThing.created.timeIntervalSince1970 > secondThing.created.timeIntervalSince1970
}.first
This will return the most resent Thing as an Optional (because there is no guarantee that the array is not empty. If you know that the array is not empty, then you can end that line with .first!

Resources