Get a limited number of records in Firebase - ios

I would like to query Firebase for records that match my conditions, but I want to only get a few of them. I am using Swift. Is this possible? For example, I have a query like this:
firebaseReference.child("array").queryOrdered(byChild: "userType").queryEqual(toValue: "family").observe(.value, with: { (snapshotVec) -> Void in
Storage.shared.numFamsInVec = snapshotVec.childrenCount
}
This will retrieve all the records whose userType is family, but say I only want to get half of them. How do I do this?

It certainly is. The magic is in the Firebase documentation for filtering data on iOS:
firebaseReference
.child("array")
.queryOrdered(byChild: "userType")
.queryEqual(toValue: "family")
.queryLimited(toFirs: 10)
.observe(.value, with: { (snapshotVec) -> Void in
Storage.shared.numFamsInVec = snapshotVec.childrenCount
}
Also see the Firebase reference documentation, which contains the magic incantation for Swift 3.

Related

Swift Firebase Query - No Results in Snapshot

I am trying to query our LCList object by the "name" value, as shown in this image. The name key is just on the next level below the object. There are no additional levels to any of its other values.
The code I am using to do the query is: (Keeping in mind listsRef points to the LCLList object)
listsRef.queryOrderedByKey()
.queryStarting(atValue: name)
.observeSingleEvent(of: .value, with: { snapshot in
The snapshot from this query comes back with nothing in its value. I have tried ordering the results by the name value as well, with the same result. I have inspected the values returned with only the queryOrderedByKey() method call, and they match what is in the database. The issue is obviously with the .queryStarting(atValue:) method call.
I'm really puzzled by why this is not working as the same query pointed to our LCLUser object, with nearly the same structure, does get results. The two objects exist at the same level in the "Objects" directory seen in the previous image.
Any help at this point would be appreciated. I'm sure there's something simple I'm missing.
That query won't work for the Firebase structure shown in the question.
The query you have is as follows, I have added a comment on each line detailing what each line does
listsRef.queryOrderedByKey() //<- examine the key of each child of lists
.queryStarting(atValue: name) //<- does that keys' value match the name var
.observeSingleEvent(of: .value, with: { snapshot in //do it once
and your current data looks like this
Objects
LCLLists
node_x
creator: "asdasa"
name: "TestList3"
node_y
creator: "fgdfgdfg"
name: "TestList"
so what's happening here is the name var (a string) is being compared to the value of each key (a Dictionary) which cannot be equal. String ≠ Dictionary
key value
["node_x": [creator: "asad", name: "TestList3"]
For that query to work, your structure would need be be like this
Objects
LCLLists
node_x: "TestList3"
node_y: "TestList"
My guess is you want to stick with your current structure so, suppose we want to query for all names that are 'TestList' using your structure
let ref = self.ref.child("LCLLists") //self.ref is a class var that references my Firebase
let query = ref.queryOrdered(byChild: "name").queryEqual(toValue: "TestList")
query.observeSingleEvent(of: .value, with: { snapshot in
print(snapshot)
})
and the result is a printout of node_y
node_y
creator: "fgdfgdfg"
name: "TestList"

Search Firebase Query with array of unique keys

I have my schema set as follows.
Now i want to send the a string chinohills7leavescafe101191514.19284 and want to check if there is any string in the Chino Hills or not.
I am confused to make any search query because i have not stored above string in fixed childKey
I know the schema should be like this but i cannot change the schema.
leaves-cafe
codes
Chino Hills
-Kw0ZtwrPjyNh1_HJrkf
codeValue: "chinohills7leavescafe101191514.19284"
You're looking for queryOrderedByValue. It works the same ways as queryOrderedByChild and allows you to use queryEqualToValue to achieve the result you need since you can't alter your current schema.
Here's an example
// warning: untested code - just illustrating queryOrderedByValue
let ref = Database.database().reference().child("leaves-cafe").child("codes").child("Chino Hills")
let queryRef = ref.queryOrderedByValue().queryEqual(toValue: "chinohills7leavescafe101191514.19284")
queryRef.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
print("value does exists")
} else {
print("value doesn't exist")
}
})
Your alternative option is to iterate over all nodes and check if the value exists manually as 3stud1ant3 suggested. However, note that this approach is both costy and a security risk. You would be downloading potentially a lot of data, and generally you shouldn't load unneeded data (especially if they're sensitive information, not sure if that's your case) on device; it's the equivalent of downloading all passwords off a database to check if the entered one matches a given user's.

Swift: Searching Firebase Database for Users

I have been looking all over the internet and Stack overflow but haven't found anything that I understood well enough to implement into my solution.
There are many similar questions out there but none of the proposed solutions worked for me, so I'll ask you guys.
How do I search my Firebase database for usernames in swift?
In my app I have a view that should allow the user to search for friends in the database. Results should be updated in real time, meaning every time the user types or deletes a character the results should update. All I have so far is a UISearchBar placed and styled with no backend code.
Since I don't really know what is needed to create such a feature it would be cool if you guys could tell me what you need to tell me exactly what to do. (Database Structure, Parts of Code...). I don't want to just copy and paste useless stuff on here.
I would really appreciate the help.
EDIT:
This is my database structure:
"users"
"uid" // Automatically generated
"goaldata"
"..." // Not important in this case
"userdata"
"username"
"email"
"country"
"..." // Some more entries not important in this case
Hope I did it right, didn't really know how to post this on here.
Suppose the Firebase structure is like this
Example 1 :
users
-> uid
->otherdata
->userdata
->username : "jen"
-> other keys
-> uid2
->otherdata
->userdata
->username : "abc"
-> other keys
And to query based on username, we need to query on queryOrdered(byChild: "userData/username"). queryStarting & queryEndingAt , fetches all the names starting from "je" and this "\uf8ff" signifies * (any) .
let strSearch = "je"
baseRef.child("users").queryOrdered(byChild: "userData/username").queryStarting(atValue: strSearch , childKey: "username").queryEnding(atValue: strSearch + "\u{f8ff}", childKey: "username").observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot)
}) { (err) in
print(err)
}
If the username is directly below the users node, then try this
Example 2 :
users
-> uid
->username : "jen"
-> other keys
-> uid2
->username : "abc"
-> other keys
The query will be restructured as below,
let strSearch = "je"
baseRef.child("users").queryOrdered(byChild: "username").queryStarting(atValue: strSearch).queryEnding(atValue: strSearch + "\u{f8ff}").observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot)
})
Please note this method is case-sensitive . The search results for search String = "je" and "Je" will be different and the query will match the case.

MapKit Define the desired type of search results (Country, city, region, etc)

For an app i'm building, I want to implement a feature that allows users to specify the geographical origin of wines (country (e.g. France), region (e.g. Bordeaux), subregion (e.g. Paullac)).
I want to make sure that I don't have to add all available countries myself, and that all information that comes into the database is valid. Therefore, I decided to do it as follows:
User adds a new wine and types the name of the country it comes from
While typing, the app searches in the apple maps database
The results from this search get displayed as suggestions, and when the user taps a suggestion, the app creates a Country object with all relevant information. The wine van only be saved when such an object is present
This works fine, except one thing: Apple maps returns anything, like restaurants, shops, etcetera, from anywhere.
My question: How can I specify WHAT I am looking for? I can only specify the region I'm searching in, which is irrelevant in my case. I would like to be able to tell apple maps to ONLY look for countries, regions, cities, whatever. Is this possible in a way? I have exhausted google for this and found no way thus far.
Going off what #Trevor said, I found rejecting results where either the title or subtitle have numbers yields pretty good results if you only want cities and towns.
Swift 4.1 code:
// Store this as a property if you're searching a lot.
let digitsCharacterSet = NSCharacterSet.decimalDigits
let filteredResults = completer.results.filter { result in
if result.title.rangeOfCharacter(from: digitsCharacterSet) != nil {
return false
}
if result.subtitle.rangeOfCharacter(from: digitsCharacterSet) != nil {
return false
}
return true
}
or more compactly:
let filteredResults = completer.results.filter({ $0.title.rangeOfCharacter(from: digitsCharacterSet) == nil && $0.subtitle.rangeOfCharacter(from: digitsCharacterSet) == nil })
The best solution we found was to filter our results using a comma in the result's title. This mostly returned only results that matched a city's format, e.g Detroit, MI, United States. We added this filter to the ones suggested by #Ben Stahl. Ben's solution filtered out edge cases where a comma formed part of the business' name.
This usually returns the correct result within three characters. To answer the OP's question, you could then parse this string by city, state or country to get the desired result.
For better results you could use the Google Places API.
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
self.searchResults = completer.results.filter { result in
if !result.title.contains(",") {
return false
}
if result.title.rangeOfCharacter(from: CharacterSet.decimalDigits) != nil {
return false
}
if result.subtitle.rangeOfCharacter(from: CharacterSet.decimalDigits) != nil {
return false
}
return true
}
self.searchResultsCollectionView.reloadData()
}
I have worked with MapKit and don't believe you can do autocomplete assistance on user entries as they type the best solution I found is Google Place API autocomplete
iOS right now provides receiving geo-coordinates when sending a well-formatted address , or you can receive an address when sending a pair of coordinates. Or points of interest for locations names or coordinates.
There was a class added to MapKit in iOS 9.3 called MKLocalSearchCompleter which helps with autocompletion. You can filter what is returned by using 'MKSearchCompletionFilterType' but that isn't the most extensive and might not fully help with your situation. It does return cities and countries as results but it also returns businesses when I've used it.
One possible option is to filter the returned results again on the app side and exclude all results that have a numeric character in them.
func setupCompleter() {
self.searchCompleter = MKLocalSearchCompleter()
self.searchCompleter?.delegate = self
self.searchCompleter?.filterType = .locationsOnly
self.searchCompleter?.queryFragment = "Bordeaux"
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
print("Results \(completer.results)")
// Do additional filtering on results here
}
In addition to Allan's answer, I've found that if you filter by the subtitle property of a MkLocalSearchCompletion object, you can remove the business entries.

More efficient way to retrieve Firebase Data?

I have a hierarchical set of data that I want to retrieve information from Firebase. Below is how my data looks:
However, my issue is this: Upon looking at how the data is structured, when I want to grab the name or object id of an attendee, I have to perform the following code:
func getAttendees(child: NSString, completion: (result: Bool, name: String?, objectID: String?) -> Void){
var attendeesReference = self.eventsReference.childByAppendingPath((child as String) + "/attendees")
attendeesReference.observeEventType(FEventType.ChildAdded, withBlock: { (snapshot) -> Void in
//Get the name/object ID of the attendee one by one--inefficient?
let name = snapshot.value.objectForKey("name") as? String
let objectID = snapshot.value.objectForKey("objectID") as? String
if snapshot != nil {
println("Name: \(name) Object ID: \(objectID)")
completion(result: true, name: name, objectID: objectID)
}
}) { (error) -> Void in
println(error.description)
}
}
Now this goes through every attendee child and grabs the name and object id one by one. When the function completes, I store each value into a dictionary. Upon doing so, this function is called multiple times and can be very slow especially when going to/from a database so many times. Is there a more efficient way to do this? I have tried to look into FEeventType.Value but that seems to return everything within the attendees child, when all I really want are the name and objectID of each attendee stored into some sort of dictionary. Any help would be appreciated. Thanks!
One of the golden rules of Firebase is to only nest your data when you always want to retrieve all of it. The reason for this rule is that Firebase always returns a complete node. You cannot partially retrieve some of the data in that node and not other data.
The Firebase guide on structuring data, says this about it:
Because we can nest data up to 32 levels deep, it's tempting to think that this should be the default structure. However, when we fetch data at a location in our database, we also retrieve all of its child nodes. Therefore, in practice, it's best to keep things as flat as possible, just as one would structure SQL tables.
You should really read that entire section of the docs, since it contains some pretty good examples.
In your case, you'll need to modify your data structure to separate the event attendees from the event metadata:
events
-JFSDFHdsf89498432
eventCreator: "Stephen"
eventCreatorId: 1764137
-JOeDFJHFDSHJ14312
eventCreator: "puf"
eventCreatorId: 892312
event_attendees
-JFSDFHdsf89498432
-JSAJKAS75478
name: "Johnny Appleseed"
-JSAJKAS75412
name: "use1871869"
-JOeDFJHFDSHJ14312
-JaAasdhj1382
name: "Frank van Puffelen"
-Jo1asd138921
name: "use1871869"
This way, you can retrieve the event metadata without retrieving the attendees and vice versa.

Resources