New Firebase Retrieve Data and Put on the Tableview (Swift)

New Firebase retrieve data and put on the tableView (Swift)

The best practice here to to populate your tableView datasource (posts array) in your ViewDidLoad method. Then your tableView isn't trying to pull down data as it's refreshing.

Also, since you are adding a .childAdded observer your app will be notified of any additions to firebase so when that happens, just add the new data to the posts array and refresh the tableView.

There's no reason to continually pull data from Firebase as it's static until it changes. So load once and then wait for those changes - you may want to consider adding a .childChanged event (and removed) in case someone updates their pic for example.

The Firechat app from Firebase is a great resource for understanding the best way to do this - if you follow that design pattern, you can avoid all networking issues and not have to worry about separate async calls.

You can simply rely on Firebase to take care of itself.

Edit... Well, while that is a Firechat link, the Obj-C code seems to be missing (thanks Firebase. ugh)

So - in light of that, see my answer to this question as it has pattern for the code you need.

Swift Firebase retrieve data into tableview

Your issue probably has to do with the fact that you're calling reloadData() from a closure, which means you're updating the UI from a background thread. Check out this answer:

Swift UITableView reloadData in a closure

Swift: Retrieve data from Firebase and display in table view

From what you have shown the culprit is the postId, you are using it to fetch data from Firebase and yet you haven't shown anywhere what its value is. When the user taps on an image, the postId is not transfered to the SiteViewController.

In the SiteViewController remove the ! and replace it with ?, put an initializer that will take the postID as a parameter.

var postId:String?

func initPost(forImage postId: String) {
self.postId=postId
}

In the previous news feed VC inside the segue or didSelectForRow(i don't know what you use for transition, initialize the SiteViewController, so when it is presented it knows which ID to retrieve data for.

Another thing that needs mentioning is that you are using observe but you are not removing the observers.

EDIT: this answer was based on me not knowing what your HomeVC looked like.

     if segue.identifier == "SiteSegue" {
let siteVC = segue.destination as! SiteViewController
let postId = sender as! String
siteVC.postId = postId

}

Read data to table view from firebase in real time

Sounds like you successfully present the data in your app, but you want your data in real time, so that your data view (tableview or whatever) is updated every time there’s a change in the database.

viewDidLoad is called only when the viewController is created, that’s why your new data is not fetched until you restart the app.

You could keep the observeSingleEvent but make the call in viewWillAppear or some other function, instead of viewDidLoad.

However, a better way is to use observe instead of observeSingleEvent. This call can be made from viewDidLoad; your data will update in real time.

Update

You can just switch to observe instead of observeSingleEvent.

self.ref.observe(.value, with: { (snapshot) in
// Handle your data
})

Please refer to the docs for more context (https://firebase.google.com/docs/database/ios/read-and-write)

Update 2

This is how you avoid duplicates in your data array:

self.ref.observe(.value, with: { (snapshot) in

// Create an array to deal with the updated data
let updatedCategoryIds = [String]()

for user_child in (snapshot.children) {

let user_snap = user_child as! DataSnapshot
let dict = user_snap.value as! [String: Any]
updatedCategoryIds.append(user_snap.key)

}

self.categoryIds = updatedCategoryIds

// Now that self.categoryIds holds real time data without duplicates you can present the data
})

Displaying Firebase data in a tableview

Update: Since PostCell is created in the storyboard within the table view it's registered and dequeued successfully. So the issue is being narrowed down to the label with tag 1. Try creating an @IBOutlet for the label and use that to set the text of UILabel.

screenshot

Then in cellForRowAt:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

guard let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell") as? PostCell else { return UITableViewCell() }
cell.firstNameLabel.text = posts[indexPath.row].firstName
return cell
}

Previous: You seem to have forgotten to register the PostCell.

override func viewDidLoad() {
super.viewDidLoad()
//...
tableView.register(PostCell.self, forCellReuseIdentifier: "PostCell")
}

Note: If you've created PostCell in Xib use nib registry method.

Update: If you want to register with Nib method use:

tableView.register(UINib(nibName: <#T##String#>, bundle: nil), forCellReuseIdentifier: "PostCell") // provide the xib file name at the placeholder

Retrieve data from firebase database to class in tableView

In your class you can create custom init method like this

// Note: Some of values are missing here

class Model {
let bookingDate: String
let bookingTime: String
let email: String
let name: String
let phonenumber: String
let studioHallAddress: String
let studioHallName: String
let surename: String
let totalSum: String

init(bookingDate: String, bookingTime: String, email: String, name: String, phonenumber: String, studioHallAddress: String, studioHallName: String, surename: String, totalSum: String) {
self.bookingDate = bookingDate
self.bookingTime = bookingTime
self.email = email
self.name = name
self.phonenumber = phonenumber
self.studioHallAddress = studioHallAddress
self.studioHallName = studioHallName
self.surename = surename
self.totalSum = totalSum
}
}

and you can create object with given details and add that to your global array and you can show to tableview

Swift 5- Firebase- Putting users into sections with data loaded from firebase


Swift 5

Declare your class as follows:

class ViewController: UIViewController
{
// MARK: Outlets

@IBOutlet weak var Tableview: UITableView!
@IBOutlet weak var field: UITextField!

// MARK: Properties

var sectionNames: [String] = []
var users: [String: [FacStaffInfo]] = [:]

var ref: DatabaseReference!

// MARK: View Controller Life Cycle

override func viewDidLoad()
{
super.viewDidLoad()
getUsersFromFirebaseDB()
}

deinit
{
ref.removeAllObservers()
}

// MARK: Private Methods

private func usersFetched(_ usersData: [FacStaffInfo])
{
for user in usersData
{
guard let userNameFirstChar = user.name.first?.uppercased() else { continue }
if var usersForKey = users["\(userNameFirstChar)"]
{
usersForKey.append(user)
users["\(userNameFirstChar)"] = usersForKey
}
else
{
// no users are stored in dictionary for key userNameFirstChar
users["\(userNameFirstChar)"] = [user]
}
}

// sort dictionary keys and set it in sectionNames
sectionNames = users.map { $0.key }.sorted()
}

private func getUsersFromFirebaseDB()
{
ref = Database.database().reference().child("users")
ref.observe(DataEventType.value, with: { [weak self] (snapshot) in

guard snapshot.childrenCount > 0 else { return }

var users: [FacStaffInfo] = []
for user in snapshot.children.allObjects as! [DataSnapshot]
{
let object = user.value as? [String: AnyObject]

let title = object?["title"]
let name = object?["name"]
let email = object?["email"]
let phone = object?["phone"]
let office = object?["office"]
let bio = object?["bio"]

let user = FacStaffInfo(title: title as! String, name: name as! String, email: email as! String, phone: phone as! Int, office: office as! String, bio: bio as! String)
users.append(user)
}

self?.usersFetched(users)
self?.Tableview.reloadData()
})
}

// MARK: Navigation

override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "showDetail"
{
if let indexPath = Tableview.indexPathForSelectedRow
{
let destinationController = segue.destination as! InfoViewController
let char = sectionNames[indexPath.section]
let user = users[char]![indexPath.row]
destinationController.FacStaffData = user
}
}
}
}

Also, add the following extension:

extension ViewController: UITableViewDataSource, UITableViewDelegate
{
func numberOfSections(in tableView: UITableView) -> Int
{
sectionNames.count
}

func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
{
sectionNames[section]
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let char = sectionNames[section]
return users[char]!.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "userCell") as! TableViewCell
let char = sectionNames[indexPath.section]
let user = users[char]![indexPath.row]
cell.nameLabel?.text = user.name
return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
performSegue(withIdentifier: "showDetail", sender: self)
tableView.deselectRow(at: indexPath, animated: true)
}
}

Output

Sample Image

Swift Tableview Load Data from Firebase Database

You are not loading the data to the SecondTableViewController instance that is presented on your screen, but to a new SecondTableViewController instance that you created in the func tableView(_:=,cellForRowAt:) method in your FirstTableViewController.
The logs are printed from the multiple instances you created from it.

This is not what you want, as you are creating multiple SecondTableViewController instances every time a new cell shows in your FirstTableViewController.
You should rather get a reference to the actual SecondTableViewController that is presented and supply the data it.

If you are using a storyboard, you can use prepare(for:sender:) to do that.
We have two choices: provide the entire data from the FirstTableViewController to SecondTableViewController using a delegate design pattern, or just provide value to SecondTableViewController and leave the fetching to it.
Based on your code, you can just supply the SecondTableViewController with value that your setIndex(value:) method in the SecondTableViewController uses, and get the data after the SecondTableViewController loads.

For example, in your SecondTableViewController:

class SecondTableViewController: UITableViewController {
...
var detailedValue: String?

override func viewDidLoad() {
super.viewDidLoad()
if let detailedValue = detailedValue {
setIndex(value: detailedValue)
}
}
...
}

and in your FirstTableViewController:

class FirstTableViewController: UITableViewController {
...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Set the segue's identifier in the Storyboard
performSegue(withIdentifier: "yourIdentifier", sender: self)
}
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourIdentifier" {
guard let destination = segue.destination as? SecondTableViewController,
let indexPath = tableView.indexPathForSelectedRow else { return }
destination.detailedValue = categories[indexPath.row]
}
}
}

But note that you already have a data to be shown on SecondTableViewController in your FirstTableViewController, so you should probably make a protocol and set FirstTableViewController its delegate.

EDIT:
Your segue should not be connected like this:
wrong segue
but like this:
correct segue



Related Topics



Leave a reply



Submit