I want to access "posts" outside this function so that I can call tableview.reloaddata() outside the function. The code under calls tableview.reloaddata() everytime a key has entered the qeoquery. I want to only reload it one time. But when I try in viewDidLoad, the "posts" array is empty. What to do?
Posts declared outside function:
var posts = [Post]()
Function:
func fetchData(){
geofireRef = Database.database().reference().child("LOCATION")
geofire = GeoFire(firebaseRef: geofireRef)
let geoQuery = geofire?.query(at: myLoc, withRadius: 5.0)
geoQuery?.observe(.keyEntered, with: { (key: String!, location: CLLocation!) in
print("KEYKEY: \(key)")
let dbRef = Database.database().reference().child("posts")
let query = dbRef.queryOrdered(byChild: "\(key)")
query.observeSingleEvent(of: .value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [DataSnapshot]{
for snap in snapshot {
if let postDict = snap.value as? Dictionary<String, Any>{
let key = snap.key
let post = Post.init(postKey: key, postData: postDict)
self.posts.append(post)
}
}
}
//self.posts.reverse()
//self.tableView.reloadData()
//print("POSTS: \(self.posts)")
})
})
}
You can either call tableView.reloadData() right after setting your posts variable (like you have commented out), or you can put a didSet observer on your posts variable and reload your tableView after it's set. If you go with that second option you'll want to restructure your parser to set the posts only once, rather than appending items one at a time. That would look something like this:
var posts = [Post]() {
didSet {
self.tableView.reloadData()
}
}
geoQuery?.observe(.keyEntered, with: { (key: String!, location: CLLocation!) in
print("KEYKEY: \(key)")
let dbRef = Database.database().reference().child("posts")
let query = dbRef.queryOrdered(byChild: "\(key)")
query.observeSingleEvent(of: .value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [DataSnapshot]{
//Create a variable to hold posts until you've parsed all of them
var foundPosts = [Post]()
for snap in snapshot {
if let postDict = snap.value as? Dictionary<String, Any>{
let key = snap.key
let post = Post.init(postKey: key, postData: postDict)
foundPosts.append(post)
}
}
//Set the posts to be all the found posts
self.posts = foundPosts
}
})
})
Related
I try to retrieve data from Firebase into Array. Because it runs asynchronously, the results that I want to show in my CollectionView is a delay until I switch back and forth. I am very new to asynchronous functions in iOS. Please help me to complete my code.
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
}
})
You need to reload the collection after you retrieve the data so after the for loop call reloadData()
for child in result {
}
self.collectionView.reloadData()
//
func getValueFromDatabase(completion: #escaping (_ status: Bool) -> Void){
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
completion(true)
}
else {
completion(false)
}
})
}
//
self.getValueFromDatabase { (status) in
if status {
// success
}
}
I'm working with Firebase in my project right now. I would suggest the following solution: wrap the database observer in a distinct function which gets completion block as a parameter.
func getValueFromDatabase(completion: ()->Void){
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
completion()
}
})
}
This way you can call the function from anywhere providing the desired action after fetching data from db is finished:
getValueFromDatabase(completion:{
self.collectionView.reloadData() //or any other action you want to fulfil
})
Please find my code below. How can we append filter data on array from Firebase?
var childrenList = [DatabaseList]()
let ref = Database.database().reference(withPath: "Messages")
let query = ref.queryOrdered(byChild: "VideoID").queryEqual(toValue: "12345").observe(.value, with: { (snapshot) in
for childSnapshot in snapshot.children{
print(childSnapshot)
self.childrenList.append(snapshot)
}
})
DispatchQueue.main.async {
self.tableView.reloadData()
}
let ref = Database.database().reference(withPath: "Messages")
let query = ref.queryOrdered(byChild: "VideoID").queryEqual(toValue: "12345").observe(.value, with: { (snapshot) in
print(snapshot)
for (childSnapshotId, childSnapshotValue) in snapshot {
if let dataListDict = childSnapshotValue as? [String: AnyObject] {
//Init you newModel with the dataListDict here
let newModel = DatabaseList(dict: dataListDict)
print(childSnapshot)
self.childrenList.append(newModel)
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
class DatabaseList : NSObject {
var messageBody : String?
var name : String?
var videoID : String?
init(dict: [String: AnyObject]) {
messageBody = dict["MessageBody"]
name = dict["Name"]
videoID = dict["videoID"]
}
}
Your query is correct but there are few mistakes in finishing block.
self.childrenList.append(snapshot) snapshot is an instance of DataSnapshot not a DatabaseList so you can not append it like this.
for childSnapshot in snapshot.children {
/// childSnapshot is an instance of DataSnapshot not a dictionary but its value will be
guard let data = (childSnapshot as! DataSnapshot).value else {continue}
let dataDict = data as! Dictionary<String, Any>
/// Initializing the new object of DatabaseList and passing the values from data
let list: DatabaseList = DatabaseList()
list.messageBody = dataDict["MessageBody"] as? String
list.name = dataDict["Name"] as? String
list.videoID = dataDict["VideoID"] as? String
/// This is correct, and now you can append it to your array.
childrenList.append(list)
}
Apart from this you will have to reload the tableView inside the finishing block not below the block because this is an asynchronous request and data will come later.
Also its always better to check the data existence. snapshot.exists().
One more suggestion if you want to fetch the data just once then do not use .observe use .observeSingleEvent instead. .observe will fire the block every time there is any change at this node.
Here is the full code snippet.
let query = ref.queryOrdered(byChild: "VideoID").queryEqual(toValue: "12345").observe(.value, with: { (snapshot) in
if !snapshot.exists() {
// Data doesn't exist
return
}
for childSnapshot in snapshot.children {
guard let data = (childSnapshot as! DataSnapshot).value else {continue}
let dataDict = data as! Dictionary<String, Any>
let list: DatabaseList = DatabaseList()
list.messageBody = dataDict["MessageBody"] as? String
list.name = dataDict["Name"] as? String
list.videoID = dataDict["VideoID"] as? String
childrenList.append(list)
}
/// Reload your tableView here
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
And expecting the class model like below:
class DatabaseList: NSObject {
var messageBody: String?
var name: String?
var videoID: String?
}
I'm working through a course on Udemy to build a chat app with Firebase, Backendless, and Swift. All of the issues (it was written for Swift 2 not 3) I've been able to resolve myself, but this one has me stumped. This function is supposed to retrieve data from the Firebase database, and apparently it was supposed to retrieve it as an NSArray, but it now retrieves it as an NSDictionary, which is making a huge list of errors in the other functions because it's not expecting a dictionary.
func loadRecents() {
firebase.childByAppendingPath("Recent").queryOrderedByChild("userId").queryEqualToValue(currentUser.objectId).observeEventType(.Value, withBlock: {
snapshot in
self.recents.removeAll()
if snapshot.exists() {
let sorted = (snapshot.value.allValues as NSArray).sortedArrayUsingDescriptors([NSSortDescriptior(key: "date", ascending: false)])
}
})
}
I've updated to Swift 3 as far as:
func loadRecents() {
ref = FIRDatabase.database().reference()
let userId = currentUser?.getProperty("username") as! String
ref.child("Recent").queryOrdered(byChild: "userId").queryEqual(toValue: userId).observe(.value, with: {
snapshot in
self.recents.removeAll()
if snapshot.exists() {
let values = snapshot.value as! NSDictionary
}
})
}
Of course, using as! NSArray does not work. Would very much appreciate it if anyone can suggest a method to update this to use Swift 3, sort it by a value in the data, and be able to access it later on. Thanks!
func loadRecents() {
ref = FIRDatabase.database().reference()
let userId = currentUser?.getProperty("username") as! String
ref.child("Recent").queryOrdered(byChild: "userId").queryEqual(toValue: userId).observe(.value, with: {
snapshot in
self.recents.removeAll()
if snapshot.exists() {
let values = snapshot.value as! [String:AnyObject]
}
})}
or you can use also let values = snapshot.value as! [Any]
Hope this will help you, try this code:
func loadRecents() {
let ref = FIRDatabase.database().reference()
let userId = currentUser?.getProperty("username") as! String
ref.child("Recent").queryOrdered(byChild: "userId").queryEqual(toValue: userId).observe(.value, with: {
snapshot in
self.recents.removeAll()
guard let mySnapshot = snapshot.children.allObjects as? [FIRDataSnapshot] else { return }
for snap in mySnapshot {
if let userDictionary = snap.value as? [String: Any] {
print("This is userKey \(snap.key)")
print("This is userDictionary \(userDictionary)")
}
}
})
}
I have a Firebase resource that contains several objects and I would like to iterate over them using Swift.
What I expected to work is the following (according to the Firebase documentation)
https://www.firebase.com/docs/ios-api/Classes/FDataSnapshot.html#//api/name/children
var ref = Firebase(url:MY_FIREBASE_URL)
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
println(snapshot.childrenCount) // I got the expected number of items
for rest in snapshot.children { //ERROR: "NSEnumerator" does not have a member named "Generator"
println(rest.value)
}
})
So it seems there is a problem with Swift iterating over the NSEnumerator object returned by Firebase.
Help is really welcome.
If I read the documentation right, this is what you want:
var ref = Firebase(url: MY_FIREBASE_URL)
ref.observeSingleEvent(of: .value) { snapshot in
print(snapshot.childrenCount) // I got the expected number of items
for rest in snapshot.children.allObjects as! [FIRDataSnapshot] {
print(rest.value)
}
}
A better way might be:
var ref = Firebase(url: MY_FIREBASE_URL)
ref.observeSingleEvent(of: .value) { snapshot in
print(snapshot.childrenCount) // I got the expected number of items
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? FIRDataSnapshot {
print(rest.value)
}
}
The first method requires the NSEnumerator to return an array of all of the objects which can then be enumerated in the usual way. The second method gets the objects one at a time from the NSEnumerator and is likely more efficient.
In either case, the objects being enumerated are FIRDataSnapshot objects, so you need the casts so that you can access the value property.
Using for-in loop:
Since writing the original answer back in Swift 1.2 days, the language has evolved. It is now possible to use a for in loop which works directly with enumerators along with case let to assign the type:
var ref = Firebase(url: MY_FIREBASE_URL)
ref.observeSingleEvent(of: .value) { snapshot in
print(snapshot.childrenCount) // I got the expected number of items
for case let rest as FIRDataSnapshot in snapshot.children {
print(rest.value)
}
}
I have just converted the above answer to Swift 3:
ref = FIRDatabase.database().reference()
ref.observeSingleEvent(of: .value, with: { snapshot in
print(snapshot.childrenCount) // I got the expected number of items
for rest in snapshot.children.allObjects as! [FIRDataSnapshot] {
print(rest.value)
}
})
A better way might be:
ref = FIRDatabase.database().reference()
ref.observeSingleEvent(of: .value, with: { snapshot in
print(snapshot.childrenCount) // I got the expected number of items
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? FIRDataSnapshot {
print(rest.value)
}
})
This is pretty readable and works fine:
var ref = Firebase(url:MY_FIREBASE_URL)
ref.childByAppendingPath("some-child").observeSingleEventOfType(
FEventType.Value, withBlock: { (snapshot) -> Void in
for child in snapshot.children {
let childSnapshot = snapshot.childSnapshotForPath(child.key)
let someValue = childSnapshot.value["key"] as! String
}
})
ref = FIRDatabase.database().reference().child("exampleUsernames")
ref.observeSingleEvent(of: .value, with: { snapshot in
for rest in snapshot.children.allObjects as! [FIRDataSnapshot] {
guard let restDict = rest.value as? [String: Any] else { continue }
let username = restDict["username"] as? String
}
})
Firebase 4.0.1
Database.database().reference().child("key").observe(.value) { snapshot in
if let datas = snapshot.children.allObjects as? [DataSnapshot] {
let results = datas.flatMap({
($0.value as! [String: Any])["xxx"]
})
print(results)
}
}
Firebase 7.3.0
Database.database().reference().child("key").observe(.value) { snapshot in
if let datas = snapshot.children.allObjects as? [DataSnapshot] {
let results = datas.compactMap({
($0.value)
})
print(results)
}
}
If you have multiple keys/values, and want to return an array with dictionary elements, declare an array:
var yourArray = [[String: Any]]()
then change block body to this:
let children = snapshot.children
while let rest = children.nextObject() as? DataSnapshot, let value = rest.value {
self.yourArray.append(value as! [String: Any])
}
I have a Firebase resource that contains several objects and I would like to iterate over them using Swift.
What I expected to work is the following (according to the Firebase documentation)
https://www.firebase.com/docs/ios-api/Classes/FDataSnapshot.html#//api/name/children
var ref = Firebase(url:MY_FIREBASE_URL)
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
println(snapshot.childrenCount) // I got the expected number of items
for rest in snapshot.children { //ERROR: "NSEnumerator" does not have a member named "Generator"
println(rest.value)
}
})
So it seems there is a problem with Swift iterating over the NSEnumerator object returned by Firebase.
Help is really welcome.
If I read the documentation right, this is what you want:
var ref = Firebase(url: MY_FIREBASE_URL)
ref.observeSingleEvent(of: .value) { snapshot in
print(snapshot.childrenCount) // I got the expected number of items
for rest in snapshot.children.allObjects as! [FIRDataSnapshot] {
print(rest.value)
}
}
A better way might be:
var ref = Firebase(url: MY_FIREBASE_URL)
ref.observeSingleEvent(of: .value) { snapshot in
print(snapshot.childrenCount) // I got the expected number of items
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? FIRDataSnapshot {
print(rest.value)
}
}
The first method requires the NSEnumerator to return an array of all of the objects which can then be enumerated in the usual way. The second method gets the objects one at a time from the NSEnumerator and is likely more efficient.
In either case, the objects being enumerated are FIRDataSnapshot objects, so you need the casts so that you can access the value property.
Using for-in loop:
Since writing the original answer back in Swift 1.2 days, the language has evolved. It is now possible to use a for in loop which works directly with enumerators along with case let to assign the type:
var ref = Firebase(url: MY_FIREBASE_URL)
ref.observeSingleEvent(of: .value) { snapshot in
print(snapshot.childrenCount) // I got the expected number of items
for case let rest as FIRDataSnapshot in snapshot.children {
print(rest.value)
}
}
I have just converted the above answer to Swift 3:
ref = FIRDatabase.database().reference()
ref.observeSingleEvent(of: .value, with: { snapshot in
print(snapshot.childrenCount) // I got the expected number of items
for rest in snapshot.children.allObjects as! [FIRDataSnapshot] {
print(rest.value)
}
})
A better way might be:
ref = FIRDatabase.database().reference()
ref.observeSingleEvent(of: .value, with: { snapshot in
print(snapshot.childrenCount) // I got the expected number of items
let enumerator = snapshot.children
while let rest = enumerator.nextObject() as? FIRDataSnapshot {
print(rest.value)
}
})
This is pretty readable and works fine:
var ref = Firebase(url:MY_FIREBASE_URL)
ref.childByAppendingPath("some-child").observeSingleEventOfType(
FEventType.Value, withBlock: { (snapshot) -> Void in
for child in snapshot.children {
let childSnapshot = snapshot.childSnapshotForPath(child.key)
let someValue = childSnapshot.value["key"] as! String
}
})
ref = FIRDatabase.database().reference().child("exampleUsernames")
ref.observeSingleEvent(of: .value, with: { snapshot in
for rest in snapshot.children.allObjects as! [FIRDataSnapshot] {
guard let restDict = rest.value as? [String: Any] else { continue }
let username = restDict["username"] as? String
}
})
Firebase 4.0.1
Database.database().reference().child("key").observe(.value) { snapshot in
if let datas = snapshot.children.allObjects as? [DataSnapshot] {
let results = datas.flatMap({
($0.value as! [String: Any])["xxx"]
})
print(results)
}
}
Firebase 7.3.0
Database.database().reference().child("key").observe(.value) { snapshot in
if let datas = snapshot.children.allObjects as? [DataSnapshot] {
let results = datas.compactMap({
($0.value)
})
print(results)
}
}
If you have multiple keys/values, and want to return an array with dictionary elements, declare an array:
var yourArray = [[String: Any]]()
then change block body to this:
let children = snapshot.children
while let rest = children.nextObject() as? DataSnapshot, let value = rest.value {
self.yourArray.append(value as! [String: Any])
}