Variable not actually set when retrieving from Firebase - swift

I am trying to get the number of children in this section of my Firebase Database, however the variable that I am setting is only locally set. Interestingly, the first print statement, prints two (which is the number I want), however, the second print statement prints zero. How can I make the function return the correct value. Thank you for the help.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
var forReturn = 0
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("gameNumber").observeSingleEvent(of: .value, with: { snapshot in
forReturn = Int(snapshot.childrenCount)
print(forReturn)
})
print(forReturn)
return forReturn
}
This prints repeating twos and zeros.

You need to declare forReturn in the class's scope and call the firebase method somewhere else. Then reload the table when you reach inside the firebase completion handler.
//In your viewDidLoad or wherever you think suitable
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("gameNumber").observeSingleEvent(of: .value, with: { snapshot in
DisptachQueue.main.async {
forReturn = Int(snapshot.childrenCount)
self.tableView.reloadData() //take an outlet of tableView
print(forReturn)
}
})

Related

Swift after iterating list.count always 0 [duplicate]

I have an array of struct called displayStruct
struct displayStruct{
let price : String!
let Description : String!
}
I am reading data from firebase and add it to my array of struct called myPost which is initialize below
var myPost:[displayStruct] = []
I made a function to add the data from the database to my array of struct like this
func addDataToPostArray(){
let databaseRef = Database.database().reference()
databaseRef.child("Post").queryOrderedByKey().observe(.childAdded, with: {
snapshot in
let snapshotValue = snapshot.value as? NSDictionary
let price = snapshotValue?["price"] as! String
let description = snapshotValue?["Description"] as! String
// print(description)
// print(price)
let postArr = displayStruct(price: price, Description: description)
self.myPost.append(postArr)
//if i print self.myPost.count i get the correct length
})
}
within this closure if I print myPost.count i get the correct length but outside this function if i print the length i get zero even thou i declare the array globally(I think)
I called this method inside viewDidLoad method
override func viewDidLoad() {
// setup after loading the view.
super.viewDidLoad()
addDataToPostArray()
print(myPeople.count) --> returns 0 for some reason
}
I want to use that length is my method below a fucntion of tableView
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myPost.count --> returns 0
}
Any help would be greatly appreciated!
You making a asynchronous network request inside closure and compiler doesn't wait for the response, so just Reload Table when get post data. replace the code with below it work works fine for you. All the best.
func addDataToPostArray(){
let databaseRef = Database.database().reference()
databaseRef.child("Post").queryOrderedByKey().observe(.childAdded, with: {
snapshot in
let snapshotValue = snapshot.value as? NSDictionary
let price = snapshotValue?["price"] as! String
let description = snapshotValue?["Description"] as! String
// print(description)
// print(price)
let postArr = displayStruct(price: price, Description: description)
self.myPost.append(postArr)
print(self.myPost.count)
print(self.myPost)
self.tableView.reloadData()
//if i print self.myPost.count i get the correct length
})
}
Firebase observe call to the database is asynchronous which means when you are requesting for the value it might not be available as it might be in process of fetching it.
That's why your both of the queries to count returns 0 in viewDidLoad and DataSource delegeate method.
databaseRef.child("Post").queryOrderedByKey().observe(.childAdded, with: { // inside closure }
Inside the closure, the code has been already executed and so you have the values.
What you need to do is you need to reload your Datasource in main thread inside the closure.
databaseRef.child("Post").queryOrderedByKey().observe(.childAdded, with: {
// After adding to array
DispatchQueue.main.asyc {
self.tableView.reloadData()
}
}

Can't view Firebase data in tableview Swift 4

Im having a issue where I can't view anything that I write to my Firebase database in my table view. I previously had some working code to view my database entries but had to revise how I write data to the database so I can save the unique ID generated by childByAutoID(), so I may delete an entry later. Here's my code:
Heres how I write to Firebase:
ref = Database.database().reference() // sets the variable "ref" to connect to our Firebase database
let key = ref?.child("task").childByAutoId().key
let post = ["uid": key, // Gets auto generated ID for database entry
"title": input.text, // Saves the title field to Firebase
"description": inputField.text] // Saves the description field to Firebase
ref?.child("task").child(key!).setValue(post) // Saves the task for Firebase, ties each post with a unique ID
var arr : [(String, String)] = [];
for (key, value) in post {
arr.append((key, value!));
Heres my TableViewController:
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return (arr.count)
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = UITableViewCell(style:
UITableViewCell.CellStyle.default, reuseIdentifier: "cell")
let (key, value) = arr[indexPath.row]; //read element for the desired cell
cell.textLabel?.text = key
cell.detailTextLabel?.text = value
return (cell)
}
override func viewDidAppear(_ animated: Bool) {//change "viewDidLoad()" back to "viewDidAppear()"
ref = Database.database().reference() // sets the variable "ref" to connect to our Firebase database
list.removeAll() // Deletes all older data, so only data thats on the Firebase Database will be added to the "list" array
desc.removeAll() // Deletes all older data, so only data thats on the Firebase Database will be added to the "desc" array
handle = ref?.child("task").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String
{
arr.append(item)
list.append(item)
//desc.removeAll()
self.myTableView.reloadData()
}
})
}
Firebase returns the items in form of NSArray or NSDictionary or NSString. (Refer here)
Since your post object is NSDictionary, parse the snapshot value as a Array of Dictionary. try the following:
if let itemArray = snapshot.value as? [[String : Any]] {
// Looping logic on 'itemArray' to get your sent dictionaries on the firebase
// After looping logic add Ui logic out of loop
}

Index Out of Range When Deleting From Firebase and UITableView

I currently have a problem that when I try to delete a row from a table, my app crashes and throws an index out of range error. The data deletes fine from the firebase database and the table, but it still throws the error.
The database is designed to store playlists, and each playlist has a dictionary of keys to the name of the song. Here is my firebase structure:
And below is the swift code (line where error is occurring has a comment)
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let song = songs[indexPath.row]
var songKey = ""
if(editingStyle == .delete){
let ref = Database.database().reference().child("Playlists/\(playlist!)")
ref.observe(.value, with: {
snapshot in
let someData = snapshot.value! as! [String: String]
for data in someData {
if(data.value == song){
songKey = data.key
break
}
}
ref.child(songKey).removeValue()
self.songs.remove(at: indexPath.row) //error on this line
self.myTableView.deleteRows(at: [indexPath], with: .fade)
self.myTableView.reloadData()
})
}
}
Thanks for any help!
I think what you should do is take another approach - handle the delete operation within its completion handler. This way you will make sure your data will be consistent. Because what happens if there is an error on your Firebase call? You have to handle this case as well. So do something like this and see what happens:
ref.child(songKey).removeValue { [weak self] error, _ in
if let error = error {
print("There was an error: ", error)
return
}
self?.songs.remove(at: indexPath.row)
self?.tableView.reloadData()
}
What I think is happening here is that your code enters an infinite loop - you use observe on your playlists then you delete. So observe's completion handler is called again and delete is called again. After you deleted the item at this index, the index cannot be found on your array anymore. So just get your playlists from the database without observing any further changes.
In this case try using observeSingleEvent.

Read the value of an attribute of a Core Data entity

I'm trying to read the value of an attribute (called 'name') of an entity (called 'List'), stored in a Core Data database. The issue that I have is that it says that the value of that attribute is nil, which should be a String.
My code for retrieving all List-entities is as follows:
container?.performBackgroundTask { [weak self] context in
self?.wordLists = try! List.all(in: context)
DispatchQueue.main.async(execute: {
print("Main queue available, reloading tableview.")
self?.wordListSelector.reloadData()
})
}
class func all(in context: NSManagedObjectContext) throws -> [List] {
let listRequest: NSFetchRequest<List> = List.fetchRequest()
do {
let list = try context.fetch(listRequest)
print(list)
return list
} catch {
print("error")
throw error
}
}
This prints:
[<__Words.List: 0x6000000937e0> (entity: List; id: 0xd00000000004000c <x-coredata://999D0158-64BD-44FD-A0B1-AB4EC03B9386/List/p1> ; data: <fault>), <__Words.List: 0x600000093a60> (entity: List; id: 0xd00000000008000c <x-coredata://999D0158-64BD-44FD-A0B1-AB4EC03B9386/List/p2> ; data: <fault>), <__Words.List: 0x600000093ab0> (entity: List; id: 0xd0000000000c000c <x-coredata://999D0158-64BD-44FD-A0B1-AB4EC03B9386/List/p3> ; data: <fault>)]
Which shows me that there should be 3 lists in the database, which is as expected.
I have created a variable like this:
var wordLists: [List] = [] {
didSet {
print("Detected wordList update, waiting for main queue.")
DispatchQueue.main.async(execute: {
print("Main queue available, reloading tableview.")
self.wordListSelector.reloadData()
})
}
}
This variable holds the List-entities that I retrieved by calling that all() function which I mentioned previously.
The following two methods will fill my TableView:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("numberOfRowsInSection: \(wordLists.count).")
return wordLists.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "CategoryCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
print("cellForRowAt: \(indexPath.row) has name \(wordLists[indexPath.row].name).")
cell.textLabel?.text = wordLists[indexPath.row].name
return cell
}
This prints the following:
Detected wordList update, waiting for main queue.
Main queue available, reloading tableview.
numberOfRowsInSection: 3.
cellForRowAt: 0 has name nil.
cellForRowAt: 1 has name nil.
cellForRowAt: 2 has name nil.
Why is the name nil? Is this because the data is still 'faulted'? By viewing topics online I thought that Core Data automaticly unfaulted its data when you try to access it. What am I doing wrong?
Edit:
If I change the didset to the following:
var wordLists: [List] = [] {
didSet {
print("Wordlist was updated.")
for wordList in wordLists {
print(wordList)
print(wordList.name)
}
}
}
It does print the names (Optional("nameofitem1")). In the cellForRowAt it still prints 'nil'.
What you do looks like a good job for the NSFetchedResultsController. That will also help with the threading you might mix up as suggested in the comments. There is really good documentation on the NSFetchedResultsController

How can I return a numberOfRowsInSection from Firebase Async

Im retrieving counts from Firebase.children the data is async Im able to retrieve the count from a function callback but i cannot get the result out of the call back and return the rows. How can I complete this? Thank you in advance.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var number:Int!
getFirData { (result) in
number = Int(result.childrenCount)
}
return number
}
This is the async data im retrieving from Firebase
func getFirData(completion:(result:FIRDataSnapshot)->()){
let dbRef = FIRDatabase.database().reference()
let userAiD = FIRAuth.auth()?.currentUser?.uid
dbRef.child("posts").queryOrderedByChild("userAiD").queryEqualToValue(userAiD).observeEventType(.Value, withBlock: {snapshot in
let snapshot = snapshot
completion(result:snapshot )
})
}
You cannot. You'll have to fire off that query in viewDidLoad() or perhaps viewWillAppear(_:), store the values of the query for reference in a property, and call reloadData() (or relevant insert methods) on your table view.