Swift variable still 0 after assigning it a value? - ios

in my viewDidLoad() i print out a the result of a function
override func viewDidLoad() {
super.viewDidLoad()
print("top count = \(getCurrentOrderNum())")
}
The function computes the value likes so
func getCurrentOrderNum() -> Int{
var orderNum = 0
ref = Firebase(url: "urlhiddenforprivacy")
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
let count = snapshot.childrenCount
orderNum = Int(count)
})
return orderNum
}
Yet it still prints 0? I tried to put var orderNum: Int = Int() at the top of my code instead of inside my getCurrentOrderNum function, but that didn't work. I know it gets the correct value inside my ref.observe function because when I ran this... it printed out the right value
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
let count = snapshot.childrenCount
orderNum = Int(count)
print(orderNum) //*****THIS PRINTS THE RIGHT VALUE****
})
return orderNum
}

You are returning orderNum from the method getCurrentOrderNum() before the asynchronous block actually runs. So at the time of return, orderNum is still 0, the initial value you set. The block completes later.
Your best option is probably to change the method to:
func getCurrentOrderNum(callback:Int->()) {
var orderNum = 0
ref = Firebase(url: "urlhiddenforprivacy")
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
let count = snapshot.childrenCount
orderNum = Int(count)
callback(orderNum)
})
}
And you would then call it like this:
override func viewDidLoad() {
super.viewDidLoad()
getCurrentOrderNum { orderNum in print(orderNum) }
}
This changes the getCurrentOrderNum() method to call back to a closure once it has finished retrieving the right value.
UPDATE: Based on comment below, the goal is to do something like this:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int)->Int {
return getCurrentOrderNum()
}
Here is an asynchronous approach for doing that:
class YourViewController : UIViewController, UITableViewDataSource {
private var orderNumber:Int = 0
private IBOutlet var tableView:UITableView!
func getCurrentOrderNum(callback:Int->()) {
ref = Firebase(url: "urlhiddenforprivacy")
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
let count = snapshot.childrenCount
callback(Int(count))
})
}
override func viewDidLoad() {
super.viewDidLoad()
getCurrentOrderNum {
orderNum in
//This code runs after Firebase returns the value over the network
self.orderNumber = orderNum // Set our orderNumber to what came back from the request for current order number
self.tableView.reloadData() // Now reload the tableView so it updates with the correct number of rows
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.orderNumber // When the view first loads, this will be 0 and the table will show nothing. After the request to Firebase returns the value, this will be set to the right number, the table view will be reloaded, and it will call this method again to get the updated number of rows to display.
}
}

In general using Firebase to return function results can be tricky - it's taking an asynchronous process and squeezing it into a synchronous process. While it can be done (as shown by Daniels good answer) there are alternatives. By looking at the code in the question, there may be a couple of important Firebase concepts that may have been overlooked.
I want to present a super simple asynchronous solution that leverages the power of Firebase.
Here's some conceptual stuff:
Define a var to keep track of the order number -
var currentOrderNumber = Int
Firebase structure
orders
order_id_00
order_num: 12345
order_id_01
order_num: 12346
Set up an observer on the orders node in viewDidLoad to notify the app when a new order is added. This will occur any time an order is written to the node so then all of the clients know what the current order numbers is:
ref.queryOnOrdersNode.childAdded { snapshot in
if let orderNumber = snapshot.value["order_num"] as? Int {
currentOrderNumber = orderNumber
}
}
and from then on whenever currentOrderNumber is printed, it will contain the actual currentOrderNumber.
It's pretty cool in that you are letting Firebase do the heavy lifting; instead of polling Firebase over and over to get the currentOrderNumbers, Firebase will tell your app what the current order number is when it changes.
You can expand on this to populate a tableView and keep it updated with additions.
firebase structure
people
person_id_0
name: "Bill"
person_id_1
name: "Larry"
and the code to populate an array and add an observer for future additions:
var namesArray = [String]
peopleNode.observeEventType(.ChildAdded) { snapshot in
if let name = child.value["name"] as? String {
namesArray.append(name)
self.tableView.reloadData
}
}
and the tableView delegate method
func tableView(tableView: UITableView, numberOfRowsInSection section: Int)->Int {
return peopleArray.count
}
Again, this lets Firebase do the heavy lifting; you don't have to poll for data as when a new person is added to the people node, Firebase tells your app and the tableView is automatically updated.
You'll notice that the code is super short and tight because your letting Firebase do most of the work for you keeping your variables updated and table populated with fresh data.
(there are typos in this code as it's conceptual)

Related

Swift: Label text Issues ( Getting Different Count of Same Array) Response from API

I'm developing a Bus Ticket Booking app. I'm trying to show the text on my label as per the following image.
So when the user fills the required data (Source City, Destination city and the Date of the journey) and hits Submit Button, the next UIViewController is presented with the filter Bus route on that particular Date.
But every time the label shows 0 count. The following is my code for the label text.
Label Code:
func setValues() {
let count2 = routes.count
DispatchQueue.main.async { [unowned self] in
self.lblRoute.text = (String(count2) + "Trip(s) Available on" + Utill.getStringFromDate(" dd MMMM yyyy,EEEE", date: self.currentDate))
}
}
And I call this function in viewDidLoad(). I use the same array Count for my numberOfRowsInSection tableView method. Here is Code table method Code.
TableView Method:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return routes.count
}
But it's now showing 0, I get the proper count(4) for my numberOfRow as you can see in the image.
So why it's showing the different count of the same Array? Is there any Concurrency issue like that?
Actually mutable array's count is depend on time, i mean it mutated like below
var routes = [0,1,2]
print(routes.count) // 3
arr = []
print(routes.count) // 0
Batter approach is:
var routes:[Trip] = [] {
didSet {
setValues() // update ui
}
}

Swift iOS - How to put timer on Firebase TransactionBlock to prevent it from increasing within a certain time period

Every time different users post something (let's say a color) I get the color they posted, the postID, their userId, the date in secs, and how many times that post was viewed.
A different user can look through a tableView and view different cells with every color that every user posted .
Every time that user who is looking taps didSelectRow to view a detail view of the color I run a Firebase TransactionBlock that increases a views count property to show how many times that particular color/cell was tapped.
For eg if the user scrolls through a tableView and see's a blueCell, a label will be on it that says views: 10 (meaning it was viewed 10 times). If that user presses that blueCell again then the views count will go show views: 11.
The problem is if that user presses that cell repeatedly then they can increase the count on that views label in matter of seconds.
How can I keep track of every object/cell that the user taps and put a timer on it so that they can't update the views count for that particular object for possibly another hour or so? I have the date in secs and postId which are unique to each object.
Basically if the user presses the blueCell at 12pm the views count for the object associated with that particular cell will go up to 11 but if they press it again anytime in between 12pm - 1pm it won't go up. After 1pm if they press it again it the views count for that object will go up to 12?
The model object and the properties I can use to identify each color object:
class ColorClass{
var color: String?
var postID: String?
var userId: String?
var date: NSNumber?
var views: NSNumber? // keeps track of how many the post was viewed
}
TableView's didSelectRow:
// the current user who is pressing the cell
let currentUserID = Auth.auth().currentUser?.uid
var colors = [ColorClass]() // 500 model objects
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return colors.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ColorsCell", for: indexPath) as! ColorsCell
cell.viewsLabel.text = colors[indexPath.row].views // I separately convert this from a NSNumber to a String
cell.colorLabel.text = colors[indexPath.row].color
return cell
}
// pressing the cell will increase the count on the object's views property
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let indexPath = tableView.indexPathForSelectedRow else { return }
// the userId on the object of the cell that was pressed
guard let userID = colors[indexPath.row].userId else { return }
guard let postID = colors[indexPath.row].postId else { return }
// make sure the current user can't update the views on their own post
if currentUserID != userID{
let viewsRef = databaseRef?.child(userID).child(postID).child("views")
viewsRef?.runTransactionBlock({
(currentData: MutableData) -> TransactionResult in
let newValue: Int
guard let existingValue = (currentData.value as? NSNumber)?.intValue else {
return TransactionResult.abort()
}
newValue = existingValue + 1
currentData.value = NSNumber(value: newValue)
return TransactionResult.success(withValue: currentData)
}, andCompletionBlock: {
(error, completion, snap) in
print(snap as Any)
if !completion{
print("The value wasn't able to update")
print(error?.localizedDescription as Any)
}else{
print("The value updated")
}
})
}
}
Just an idea.
I thought about creating another object that would have a currentUserID, postID, and tappedTime properties. Then I would create a singleton. Every time a cell is pressed I’d pass the data into the object then send the object over to an array in the singleton. In there I’d have a currentTime property. First I’d check if the postID is in the array and if so I’d compare the tappedTime to the currentTime + 1 hour to decide if the views count should get increased. I’d have a dispatch asynch timer and after 1 hour it would automatically get purged from the array. I’m not sure how practical it is though.
You could create a typealias consisting of whatever the object is you're populating your cells with and a Date at the top of your view controller, like so:
typealias ColorLastSelected = (colorClass: ColorClass, timeSelected: Date)
Then, create an array to store the ColorLastSelected objects.
var selectedColors: [ColorLastSelected] = []
From there, in didSelectRow, you could do a guard statement to check if an object is contained within the selectedColors array. If not, then do whatever it is you've got to do and at the end, initialize a ColorLastSelected object and append it to the selectedColors array.
In terms of keeping the selectedColors up to date, you could run an update method on a repeating timer to remove ColorLastSelecteds that are over 1 hour old. Alternatively, you could just filter the selectedColors array before the guard statement to remove stuff that's over an hour old. If you're going to be jumping around between view controllers, you may need to create a singleton that "stays alive" or you could persist the selectedColors array somewhere
The idea I had at the bottom of the question worked.
I basically made a ViewsTrackingObject with a property specifically for the postId
I then made a singleton that adds the viewsTrackingObject to an array, checks to see if its in the array, if not add it to the array, then remove it from the array after xxx secs.
For this example I set it to 15 secs inside step 9: .now() + 15 but if I wanted it for an hour I would change it to .now() + 3600.
I find it easier to explain things in steps. There are 0 - 21 steps. I listed the steps as commented out code above each corresponding piece of code starting at the top of the Tracker class with step 0 and it ends the bottom of didSelectRow with step 21
ViewsTrackingObject:
class ViewsTrackingObject{
var postId: String?
}
Singleton Class:
class Tracker{
static let sharedInstance = Tracker()
var viewsObjects = [ViewsTrackingObject]()
var updateCount = false // 0. need to access this inside didSelectRow (step 17 )to find out wether or not to update the number of views. This would set to true in step 3 below
func checkForObjectInArray(object: ViewsTrackingObject){
// 1. check to see if the object is in the array. If it is return true if not return false. Use dot notation to compare the postId on the viewsTrackingObject vs what's inside the array to find out if it exists
let boolVal = viewsObjects.contains(where: {$0.postId == object.postId})
// 2. if the object is NOT inside the array then append to the array and then add it to the function that will remove it from the array in whatever secs you specify from the moment it's added. I specified 15 secs
if !boolVal{
updateCount = true // 3. change this to true which means in didSelectRow in step 18 return TransactionResult.success(withValue: currentData) will run
viewsObjects.append(object) // 4. add it to the above array property
removeObjectFromArray(object) // 5. will remove the viewsTrackingObject passed into the object parameter above in 15 secs from now. Look at step 9
}
}
// 6. this is called above when an object is appended to the array
func removeObjectFromArray(_ object: ViewsTrackingObject){
// 7. even though the object should definitely be inside the array double check. If it's in there return true if not return false
let boolVal = viewsObjects.contains(where: {$0.postId == object.postId})
// 8. if the object is in the array which mean the boolVal is true then proceed to step 9
if boolVal{
// 9. Fire off in 15 secs from now
DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
// 10. find the index of the viewsTrackingObject inside the array
if let index = self.views.index(where: {$0.postId == viewsModel.postId}){
// 11. remove the viewsTrackingObject at the corresponding index from the array
self.viewsObjects.remove(at: index)
print("++++SUCCESS OBJECT REMOVED++++") // in 15 secs these print statements will print to the console
print("----viewsObjects count: \(views.count)")
print("....viewsObjects items: \(views.description)")
}
}
}
}
}
The class that contains the tableView. Declare a property for the Tracker's sharedInstance so everything runs through the Singleton class
// 12. This is declared as a class property and it's used in didSelectRow. Its the Singleton Class
let tracker = Tracker.sharedInstance
let currentUserID = Auth.auth().currentUser?.uid // the current user who is pressing the cell
var colors = [ColorClass]() // 500 model objects
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let indexPath = tableView.indexPathForSelectedRow else { return }
// 14. Get the postId of the colorObject corresponding to the tapped cell
guard let postID = colors[indexPath.row].postId else { return }
guard let userID = colors[indexPath.row].userId else { return } // the userId on the object of the cell that was pressed. This is used as a child in the databaseRef below to update the user's view's property
// make sure the current user can't update the views on their own post
if currentUserID != userID{
// 15. Create a ViewsTrackingObject and set it's postID property to the same postId property from step 14
let viewsTrackingObject = ViewsTrackingObject()
viewsTrackingObject.postId = postID
// 16. using the tracker's shared instance, call the method to find out if the object is currently inside the Singleton's array
tracker.checkForObjectInArray(object: viewsTrackingObject)
let viewsRef = databaseRef?.child(userID).child(postID).child("views")
viewsRef?.runTransactionBlock({
(currentData: MutableData) -> TransactionResult in
let newValue: Int
guard let existingValue = (currentData.value as? NSNumber)?.intValue else {
return TransactionResult.abort()
}
newValue = existingValue + 1
currentData.value = NSNumber(value: newValue)
// 17. check to see if the singleton's updateCount property was set to true in step 3. If is true then proceed to step 18
if self.tracker.updateCount{
// 18. reset singleton's updateCount property back false since it was set to true in step 3
self.tracker.updateCount = false
print("*****Views Updated")
return TransactionResult.success(withValue: currentData)
}
// 19. if the singleton's updateCount property was false to begin with then the views won't get updated in firebase because the transaction will get aborted
print("=====Views NOT Updated")
return TransactionResult.abort()
}, andCompletionBlock: {
(error, completion, snap) in
print(snap as Any)
if !completion{
// 20. If something went wrong reset singleton's updateCount property back false
self.tracker.updateCount = false
print("The value wasn't able to update")
print(error?.localizedDescription as Any)
}else{
// 21. it's unnecessary but to be on the safe side
self.tracker.updateCount = false
print("The value updated")
}
})
}
}

Use a function that needs UITableView in another view?

In my WalletTableViewController I have two functions, used to calculate the Wallet Value:
A. updateCellValue() Is called by reloadData() with the tableView and uses indexPath.row to fetch a value (price) and an amount (number of coins) corresponding to the cell and make a calculation to get the total value of that coin (amountValue = value * amount). That is then saved with Core Data.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! WalletTableViewCell
cell.delegate = self
cell.amountTextField.delegate = self
updateCellValue(cell, atRow: indexPath.row)
return cell
}
func updateCellValue(_ walletTableViewCell: WalletTableViewCell, atRow row: Int) {
var newCryptos : [CryptosMO] = []
var doubleAmount = 0.0
if CoreDataHandler.fetchObject() != nil {
newCryptos = CoreDataHandler.fetchObject()!
}
cryptoPrice = cryptos[row].code!
guard let cryptoDoublePrice = CryptoInfo.cryptoPriceDic[cryptoPrice] else { return }
let selectedAmount = newCryptos[row]
guard let amount = selectedAmount.amount else { return }
var currentAmountValue = selectedAmount.amountValue
doubleAmount = Double(amount)!
let calculation = cryptoDoublePrice * doubleAmount
currentAmountValue = String(calculation)
CoreDataHandler.editObject(editObject: selectedAmount, amount: amount, amountValue: currentAmountValue)
updateWalletValue()
}
B. updateWalletValue() Is a function that fetches all the amountValue objects in Core Data and adds them together to calculate the Wallet Value.
func updateWalletValue() {
var items : [CryptosMO] = []
if CoreDataHandler.fetchObject() != nil {
items = CoreDataHandler.fetchObject()!
}
total = items.reduce(0.0, { $0 + Double($1.amountValue)! } )
WalletTableViewController.staticTotal = total
}
In my MainViewController, the Wallet Value is displayed too, but how can I refresh it's value?
func updateMainVCWalletLabel() {
//... what can I do here??
}
This works great for the WalletViewController of course with the TableView and indexPath, but how can I call updateCellValue from the MainViewController to keep the value updated?
The WalletViewController is instantiated and pushed from the MainViewController :
#IBAction func walletButtonTapped(_ sender: Any) {
let walletViewController = self.storyboard?.instantiateViewController(withIdentifier: "walletTableViewController")
self.present(walletViewController!, animated: true)
}
If you want to use a single method in multiple view controllers you should implement that method where you can call that method from anywhere. For example you can use singleton class here.
Create a swift file and name it as your wish (like WalletHelper or WalletManager)
Then you will get a file with the following format
class WalletHelper: NSObject
{
}
Create a shared instance for that class
static let shared = WalletHelper()
Implement the method you want
func getWalletValue() -> Float {
// write your code to get wallet value`
// and return the calculated value
}
Finally call that method like
let walletValue = WalletHelper.shared. getWalletValue()
WalletHelper.swift looks like
import UIKit
class WalletHelper: NSObject
{
static let shared = WalletHelper()
func getWalletValue() -> Float {
// write your code to get wallet value
// and return the calculated value
}
}
Update (old answer below)
To me it is absolutly unclear what you want to achieve: Which value do you want to be updated? The staticTotal?
Seems a litte like an XYProblem to me. As #vadian commented yesterday, please clearly describe where the data is stored, how the controllers are connected, what you want to update when in order to achieve what. You could also provide a MCVE which makes clear what you are asking, instead of adding more and more code snippets.
And, even more interesting: Why do you modify CoreData entries (CoreDataHandler.editObject) when you are in the call stack of tableView(_: cellForRowAt:)? Never ever ever do so! You are in a reading case - reloadData is intended to update the table view to reflect the data changes after the data has been changed. It is not intended to update the data itself. tableView(_: cellForRowAt:) is called many many times, especially when the user scrolls up and down, so you are causing large write impacts (and therefore: performance losses) when you write into the database.
Old Post
You could just call reloadData on the table view, which then will update it's cells.
There are also a few issues with your code:
Why do you call updateWalletValue() that frequently? Every time a cell is being displayed, it will be called, run through the whole database and do some reduce work. You should cache the value and only update it if the data itself is modified
Why do you call fetchObject() twice (in updateWalletValue())?
You should do this:
func updateWalletValue() {
guard let items:[CryptosMO] = CoreDataHandler.fetchObject() else {
WalletTableViewController.staticTotal = 0.0
return
}
total = items.reduce(0.0, { $0 + Double($1.amountValue)! } )
WalletTableViewController.staticTotal = total
}

Array of struct not updating outside the closure

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()
}
}

Swift 2 Firebase TableView

I am a newbie to swift and firebase, I am trying to populate my tabelview with firebase data. When I run the program, nothing shows up in tableview. Any help would be gladly appreciated. This is what I got do far, tried to read the documents, but its not helping.
import UIKit
import Firebase
import FirebaseUI
class ChurchTableViewController: UITableViewController {
let firebase = Firebase(url:"https://.....com/")
var items = [NSDictionary]()
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
//self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func viewDidAppear(animated: Bool) {
//MARK: Load data from firebsr
firebase.observeEventType(.Value, withBlock: { snapshot in
print(snapshot.value)
}, withCancelBlock: { error in
print(error.description)
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let dict = items[indexPath.row]
cell.textLabel?.text = dict["ChurchName"] as? String
return cell
}
You've created the observer for when some value changes in your Firebase DB, but in your closure you need to add the new items and of course reload your UITableView to synchronize the data in your app, see the following code to see a sample of how to do it with a sample data type too:
var items = [GroceryItem]()
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
firebase.observeEventType(.Value, withBlock: { snapshot in
var newItems = [GroceryItem]()
for item in snapshot.children {
let itemType = GroceryItem(snapshot: item as! FDataSnapshot)
newItems.append(itemType)
}
// update your item with the new ones retrieved
self.items = newItems
// reload the data
self.tableView.reloadData()
})
}
In the below struct you can see a sample of how you can create your data type from the data returned from Firebase
GroceryItem
struct GroceryItem {
let key: String!
let name: String!
let addedByUser: String!
let ref: Firebase?
var completed: Bool!
// Initialize from arbitrary data
init(name: String, addedByUser: String, completed: Bool, key: String = "") {
self.key = key
self.name = name
self.addedByUser = addedByUser
self.completed = completed
self.ref = nil
}
init(snapshot: FDataSnapshot) {
key = snapshot.key
name = snapshot.value["name"] as! String
addedByUser = snapshot.value["addedByUser"] as! String
completed = snapshot.value["completed"] as! Bool
ref = snapshot.ref
}
}
For a deeper knowledge about how to use Firebase you can read this very good tutorial:
Firebase Tutorial: Getting Started
I hope this help you.
Check that you have set your Tableview's delegate and datasource properly, to do this, go to interface builder, cmd + right click on your tableview and drag over to the yellow heading icon in interface builder.
You should see two options, 'datasource' and 'delegate', make sure that they are both checked and then rerun your app, you should see the table populate with whatever data you've loaded
You've got three issues
1) Your not populating a datasource for your tableview. This is typically an array that is stored in the class and because it's by .value you will need to iterate over those values to get to each child nodes data
2) You are observing by .value. This will return everything in the node, all children, their children etc so you won't be able to directly read it as a string value unless that's all the node contains, as in a single key:value pair, otherwise all of they key:value pairs will be read.
3) Firebase is asynchronous so within the observe block, you need to populate the array, and then re-load the tableview
Here's the solution:
Given a structure
users
user_id_0
name: "Biff"
user_id_1
name: "Buffy"
user_id_2
name: "Skip
here's the associated code to read in each name and populate a namesArray
var namesArray: [String] = []
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
for child in snapshot.children {
let name = child.value["name"] as! String
namesArray.append(name)
}
self.myTableView.reloadData()
})
substitute your items array for the namesArray.
They key is to let Firebase load the data asynchronously before telling the tableView to refresh itself, and when using .Value, ensure you iterate over all of the children in that node with snapshot.children
This is happened because there is no data in your items array. So first inside your viewDidAppear method you need to append your Firebase data dictionaries into items array and then call tableView.reloadData().
Also check your Firebase database url is correct and you need to fetch and store data in proper format while appending to items array.

Resources