Data is not displayed in TableView from Firebase - swift

I have a 2 problems with displaying data in a table from Firebase.
Nothing displayed in TableView from Firebase
I I can not add a link(child) to a variable
Print is working. I get access to Firebase, but nothing is added to TableView. Please, look at my code and correct where i'm wrong.
It's my model
class Exercises {
var titleExercise = ""
var descriptionExercise = ""
init (titleExercise: String, descriptionExercise: String) {
self.titleExercise = titleExercise
self.descriptionExercise = descriptionExercise
}
}
It's my ViewController
class ExercisesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
//MARK: Properties
var refWorkout: String = ""
var workout: TrainingProgram?
var ref: DatabaseReference!
#IBOutlet weak var tableView: UITableView!
var exercises = [Exercises]()
//MARK: Methods
override func viewDidLoad() {
super.viewDidLoad()
fetchExercises()
tableView.dataSource = self
tableView.delegate = self
refWorkout = workout!.title
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return exercises.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ExercisesTableViewCell
let workouts = exercises[indexPath.item]
cell.titleLabel.text = workouts.titleExercise
cell.descriptionLabel.text = workouts.descriptionExercise
return cell
}
func fetchExercises() {
Database.database().reference().child("programs").child("OPEN SPACE").child("exercises").observe(.childAdded) { (snapshot) in
print(snapshot.value)
if let dict = snapshot.value as? [String: AnyObject] {
let newTitle = dict["title"] as! String
let newDescription = dict["description"] as! String
let exerciseTableCell = Exercises(titleExercise: newTitle, descriptionExercise: newDescription)
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
And I have second question. It also addresses this issue.
As you can see, I have refWorkout = workout!.title Here comes the title from previous ViewController , and refWorkout is a child for Firebase. If I will write next code
ref = Database.database().reference().child("programs").child(refWorkout).child("exercises")
ref.observe(.childAdded) { (snapshot) in
print(snapshot.value)
}
Everything will work. Print will work. But if I insert this code to func fetchExercises() - > It will look like
func fetchExercises() {
Database.database().reference().child("programs").child(refWorkout).child("exercises").observe(.childAdded)...
My app crashed.
Please help me with two questions. Thank you!
My Firebase structure

This is a common mistake, you are reloading the table view too soon and you don't assign/append the result to the data source array
The observe API works asynchronously, put the line to reload the table view into the closure
func fetchExercises() {
Database.database().reference().child("programs").child("OPEN SPACE").child("exercises").observe(.childAdded) { (snapshot) in
print(snapshot.value)
if let dict = snapshot.value as? [String: Any] { // most likely all values are value type
let newTitle = dict["title"] as! String
let newDescription = dict["description"] as! String
let exercise = Exercises(titleExercise: newTitle, descriptionExercise: newDescription)
self.exercises.append(exercise)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
Side note:
You class contains 3 bad practices:
Semantically objects used in collection types should be named in singular form.
Don't declare properties with default values if there is an initializer.
There is too much redundant information in the variable names
And in most cases a struct and even constants are sufficient. I'd recommend
struct Exercise {
let title : String
let description : String
}
In a struct you get the initializer for free.

Related

Table view not updating with Firebase

I am trying to get a table view to update from my Firebase database. My data is structured like this (for reporting automobiles):
Reports:
Randomly generated report ID number:
make: "make of car"
model: "make of model"
I don't think that I am calling the data correctly. I do not know how to select for the random report ID. But there may be something else that I am missing. I am trying to get only the make and model of the vehicle to display in the text of the cell
var reportList:[String] = []
var ref: DatabaseReference!
var handle: DatabaseHandle?
#IBOutlet weak var reportsTableView: UITableView!
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
self.performSegue(withIdentifier: "reportsToHome", sender: self)
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return reportList.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
cell.textLabel?.text = reportList[indexPath.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
handle = ref.child("Reports").observe(.childAdded, with: { (snapshot) in
if (snapshot.value as? String) != nil
{
let make = String(describing: self.ref.child("Reports").child("make"))
let model = String(describing: self.ref.child("Reports").child("model"))
self.reportList.append(make + model)
self.reportsTableView.reloadData()
}
}
// Do any additional setup after loading the view.
)}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I'm not able to test this, but I have feeling that you were trying to cast the snapshot as a string. Instead, you should cast it as a Dictionary so you can easily retrieve the data by key.
Try this code. It sets the snapshot as a dictionary, and from there you are able to retrieve the make and model:
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
handle = ref.child("Reports").observe(.childAdded, with: { (snapshot) in
if let reports = snapshot.value as? NSDictionary {
var make = reports?["make"] as? String
var model = reports?["model"] as? String
self.reportList.append(make + model)
self.reportsTableView.reloadData()
}
}
)}
Going further, you can create a Report class:
class Report: NSObject {
var make: String?
var model: String?
}
You can then set the make and model from the snapshot to create a new Report object.
var make = reports?["make"] as? String
var model = reports?["model"] as? String
let newReport = Report()
newReport.setValuesForKeys(reports)
I hope this works, if not I'll look again.

How to access a property of a tableview cell from viewcontroller?

Please see screenshot. There is a repliesTableView, replyTextField and replyButtonin ViewController. repliesTableView cell is called ReplyCell. In ReplyCell there is a commentTableView to list all comments for that reply and a textfField, a commentButton to add new comments.
I have problem when add new replies and new comments. I guess I need to make comments array in ReplyCell empty when I click the Reply button. How can I make this happen? I have no idea how to access comments arrayfrom the root ViewController.
Exact problems: fter clicking commentButton, all comments in every cell doubled. After clicking replyButton, comments went to wrong cell.
Code:
import UIKit
import Firebase
class TopicForumVC: UIViewController, UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet weak var topicNameLabel: UILabel!
#IBOutlet weak var replyNumberLabel: UILabel!
#IBOutlet weak var repliesTableView: UITableView!
#IBOutlet weak var replyTextField: UITextField!
var topicName:String?
var firstKey:String?
var secondKey:String?
var replies = [String]()
var replyButtonTapped = false
override func viewDidLoad() {
super.viewDidLoad()
repliesTableView.delegate = self
repliesTableView.dataSource = self
replyTextField.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
topicNameLabel.text = self.topicName
loadReplies()
}
func loadReplies() {
self.replies = []
DataService.ds.Categories_Base.child(self.firstKey!).child("Topics").observe(.value, with:{(snapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
if let topicDict = snap.value as? Dictionary<String,AnyObject> {
if let topic = topicDict["text"] as? String {
if topic == self.topicName {
self.secondKey = snap.key
UserDefaults.standard.setValue(snap.key, forKey: Key_SecondKey)
if let replyDict = topicDict["replies"] as? Dictionary<String,AnyObject> {
for eachDict in replyDict {
if let textDict = eachDict.value as? Dictionary<String,AnyObject> {
if let reply = textDict["text"] as? String {
self.replies.append(reply)
self.replyNumberLabel.text = String(self.replies.count)
}
}
}
}
}
}
}
}
self.repliesTableView.reloadData()
}
})
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return replies.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "ReplyCell") as? ReplyCell {
let reply = replies[indexPath.row]
cell.configureReplyCell(reply: reply)
return cell
} else {
return UITableViewCell()
}
}
#IBAction func replyButtonTapped(_ sender: Any) {
replyButtonTapped = true
if let reply = replyTextField.text, reply != "" {
self.replies = []
DataService.ds.Categories_Base.child(self.firstKey!).child("Topics").child(self.secondKey!).child("replies").childByAutoId().child("text").setValue(reply)
self.repliesTableView.reloadData()
let i = replies.count
for n in 0..<i {
let indexPath = IndexPath(row: n, section: 1)
let cell = repliesTableView.cellForRow(at: indexPath) as! ReplyCell
cell.comments = []
cell.repliesToReplyTableView.reloadData()
}
self.replyTextField.text = ""
self.replyButtonTapped = false
}
}
}
import UIKit
import Firebase
class ReplyCell: UITableViewCell,UITableViewDataSource,UITableViewDelegate, UITextFieldDelegate {
#IBOutlet weak var replyTextView: UITextView!
#IBOutlet weak var repliesToReplyTableView: UITableView!
#IBOutlet weak var commentTextField: UITextField!
var reply:String?
var comments = [String]()
var replyKey:String?
override func awakeFromNib() {
super.awakeFromNib()
self.comments = []
repliesToReplyTableView.delegate = self
repliesToReplyTableView.dataSource = self
commentTextField.delegate = self
loadComments()
}
func configureReplyCell(reply:String) {
self.reply = reply
self.replyTextView.text = self.reply
}
func loadComments() {
self.comments = []
if let firstKey = UserDefaults.standard.value(forKey: Key_FirstKey) as? String, let secondKey = UserDefaults.standard.value(forKey: Key_SecondKey) as? String {
DataService.ds.Categories_Base.child(firstKey).child("Topics").child(secondKey).child("replies").observe(.value, with:{(snapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshots {
if let replyDict = snap.value as? Dictionary<String,AnyObject> {
if let reply = replyDict["text"] as? String {
if reply == self.reply {
self.replyKey = snap.key
DataService.ds.Categories_Base.child(firstKey).child("Topics").child(secondKey).child("replies").child(snap.key).child("comments").observe(.value, with: { (commentSnapshot) in
if let commentSnapshots = commentSnapshot.children.allObjects as? [FIRDataSnapshot] {
for commentSnap in commentSnapshots {
if let commentDict = commentSnap.value as? Dictionary<String,AnyObject> {
if let comment = commentDict["text"] as? String {
self.comments.append(comment)
}
}
}
}
self.repliesToReplyTableView.reloadData()
})
}
}
}
}
}
})
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comments.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let commentCell = tableView.dequeueReusableCell(withIdentifier:"CommentCell")
commentCell?.textLabel?.text = comments[indexPath.row]
return commentCell!
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
#IBAction func commentBtnPressed(_ sender: Any) {
if let comment = commentTextField.text, comment != "" {
self.comments = []
if let firstKey = UserDefaults.standard.value(forKey: Key_FirstKey) as? String, let secondKey = UserDefaults.standard.value(forKey: Key_SecondKey) as? String {
DataService.ds.Categories_Base.child(firstKey).child("Topics").child(secondKey).child("replies").child(self.replyKey!).child("comments").childByAutoId().child("text").setValue(comment)
if let myViewController = parentViewController as? TopicForumVC {
// myViewController.repliesTableView.reloadData()
myViewController.replies = []
}
self.repliesToReplyTableView.reloadData()
self.commentTextField.text = ""
self.replyKey = ""
}
}
}
I don't really know the exact circumstances of what you're building but there are two ideas that may offer some guidance.
1) If your table is displaying content from a data source then you will likely have some kind of reference. E.g. when loading the cells (in this case CustomCell) you'll do something like get the index of the cell and get the same index from the data, and put that data in the cells content. If that's the case, all you have to do on the button click is use tableview.cellForRowAtIndexPath with your sender object, and then remove the array from the data source, e.g. tableDataSource[index] = nil and reload the tableView.
2) If you have a stored property on the CustomCell that you've add specifically for storing this array, then you'd cast the sender object to CustomCell and remove the property, as in Kim's answer.
Hope this helps, but without more information it's kind of hard to tell.
let cell = tableview.cellForRowAtIndexPath(...) as? CustomCell
if cell != nil {
let arr = cell.array
}
BTW: I would re-think storing your array in the cell..

Swift: Retrieve data from Firebase Database to label

I'm trying to get my data from Firebase Database to particular label in Swift. I have two labels in TableView (as Main.storyboard) tagged 1 and 2.
In a ViewController, I have this code:
import UIKit
import Firebase
import FirebaseDatabase
struct confStruct {
let title : String!
let place : String!
}
class EVS_Table_VC: UITableViewController {
var conf = [confStruct]()
override func viewDidLoad() {
super.viewDidLoad()
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("conferences").queryOrderedByKey().observeEventType(.ChildAdded, withBlock: {
snapshot in
self.tableView.reloadData()
})
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return conf.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell")
let label1 = cell?.viewWithTag(1) as! UILabel
label1.text = conf[indexPath.row].title
let label2 = cell?.viewWithTag(2) as! UILabel
label2.text = conf[indexPath.row].place
return cell!
}
}
But nothing shows up in a Simulator. Does somebody have a proposition how to resolve this? This struct confStruct initializes my variable from
Database? (title, place).
JSON tree:
"conferences": {
"Key": {
"date": "some date"
"deadline": "some deadline"
"place": "some place"
"title": "some title"
}
}
Change your struct to :-
struct confStruct {
let title : String!
let place : String!
init(title_String : String!, place_String : String!){
self.title = title_String
self.place = place_String
}
}
And:-
FIRDatabase.database().reference().child("conferences/Key").observeSingleEvent(of: .value, with: {(snap) in
if let snapDict = snap.value as? [String:AnyObject]{
let titleS = snapDict["title"] as! String
let placeS = snapDict["place"] as! String
let temp = confStruct.init(title_String: titleS, place_String: placeS)
self. conf.append(temp)
self.tableView.reloadData()
}
})

populate UITableView with data stored in Firebase database

I have a UITableVIew in my iPhone application that populates each cell using an array but the question is how do you populate the array with values retrieved from the firebase database:
func retData(){
rootRef.child("users").child("Test").observeEventType(.Value){
(snap: FIRDataSnapshot) in
}
}
var no1 = ["3","6","3","4","5","20","34","34"]
Table Code:
func tableView(tableView: UITableView, cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
let cell1 = self.h_table.dequeueReusableCellWithIdentifier("cell1",
forIndexPath: indexPath) as! Hole_Cell
cell1.s_label.text = usersArray[indexPath.row]
firebase data Structure:
--Users
--Test: 1
--Test1: 2
--Test2: 3
--Test4: 4
Try this:
var usersArray: [String]?
func retData() {
rootRef.child("users").observeEventType(.Value, withBlock: { snapshot in
usersArray = [String]()
for user in snapshot.allObjects as! [FIRDataSnapshot] {
let userString = user.value as? String
usersArray?.append(userString!)
}
})
}
***Edit Try this:
var usersArray = [String]()
func retData() {
rootRef.child("users").observeEventType(.Value, withBlock: { snapshot in
let usersDict = snapshot.value as! [String:String]
self.usersArray= Array(usersDict.values)
self.tableView.reloadData()
}
})
}
*** Again Edit: Add this within the closure to account for when users has no data.
if let _ = snapshot.value as? NSNull {
return
} else {
let usersDict = snapshot.value as! [String:String]
self.usersArray= Array(usersDict.values)
self.tableView.reloadData()
}
Check my latest Questions, basically the same stuff. Checkout Firebase UI, very useful.
I used a ViewController with a table view and a Prototype Cell in it. Then this worked for me:
import Firebase
import FirebaseDatabaseUI
class LinksViewController: UIViewController, UITableViewDelegate {
var dataSource: FirebaseTableViewDataSource?
var ref = FIRDatabase.database().reference()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
dataSource = FirebaseTableViewDataSource.init(query: myQuery(),prototypeReuseIdentifier: "<Your-Identifier>", view: self.tableView)
dataSource?.populateCellWithBlock { (cell: UITableViewCell, obj: NSObject) -> Void in
//Populate your Cell here
}
tableView.dataSource = dataSource
tableView.delegate = self
}
}
Note that the myQuery is basically then your first line.

Querying parse for objects with a Pointer, swift

I'm trying to make a Query with a Pointer in Parse. I have two classes "Discover" and "DiscoveryDetails". I want to get the discovery details of an object that's picked from the discovery class.
Discovery Class
DiscoveryDetails Class - with the discoverID as the pointer.
The discovery objects are displayed in a DiscoveryTableView and on selecting one of the items, I want to query the objects of with an ID related to that selection in a DiscoveryDetailsTableView.
The DiscoveryTableView shows the objects as they appear in the class but the DiscoveryDetailsTableView shows all the objects instead of those related to the Cell I selected.
This is my didSelectRowAtIndexPath Code in the DiscoveryTableView:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let mainStoryboard = UIStoryboard(name: "Discovery", bundle: nil)
let discoveryDetailView = mainStoryboard.instantiateViewControllerWithIdentifier("discoveryDetailTVC") as! DiscoveryDetailTableViewController
let object = self.objectAtIndexPath(indexPath)
discoveryDetailView.titleString = object?.objectForKey("workoutName") as! String
discoveryDetailView.describtionString = object?.objectForKey("workoutDetails") as! String
discoveryDetailView.numberOfWorkouts = object?.objectForKey("numberOfWorkouts") as! Int
discoveryDetailView.imageFile1 = object?.objectForKey("image1") as! PFFile
discoveryDetailView.imageFile2 = object?.objectForKey("image2") as! PFFile
discoveryDetailView.imageFile3 = object?.objectForKey("image3") as! PFFile
let row = indexPath.row //we know that sender is an NSIndexPath here.
let selectedObj = objects![row] // some var where you hold your data
discoveryDetailView.varInDDT = selectedObj
self.navigationController?.pushViewController(discoveryDetailView, animated: true)
}
In my DiscoveryDetailsTableView I have this code:
var titleString: String!
var describtionString: String!
var numberOfWorkouts: Int!
var imageFile1: PFFile!
var imageFile2: PFFile!
var imageFile3: PFFile!
var varInDDT : PFObject?
//MARK: Query for Table with the details
override func queryForTable() -> PFQuery {
let discoveryQuery = PFQuery(className: "DiscoveryDetails")
discoveryQuery.cachePolicy = .CacheElseNetwork
discoveryQuery.whereKey("discoveryID", equalTo: PFObject(withoutDataWithClassName: "Discovery", objectId: "\(varInDDT!.objectId!)"))
discoveryQuery.orderByDescending("createdAt")
return discoveryQuery
}
override func viewDidLoad() {
super.viewDidLoad()
//Header Display
let imagesArray = [imageFile1, imageFile2, imageFile3]
let imagePicked = randomIntergerInRange(0, high: imagesArray.count)
titleLabel.text = titleString.uppercaseString
self.subtitleLabel.text = describtionString
self.numberOfWorkoutsLabel.text = "\(numberOfWorkouts!) Workouts"
//Pick a random Image from the Images
imagesArray[imagePicked].getDataInBackgroundWithBlock({ (imageData, error) -> Void in
if error == nil
{
if let imageData = imageData
{
let image = UIImage(data:imageData)
self.backgroundImage.image = image
}
}
})
...
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
var discoveryDetailItemsCell:DiscoveryDetailTableViewCell! = tableView.dequeueReusableCellWithIdentifier("DiscoveryDetailTableViewCell") as? DiscoveryDetailTableViewCell
...
discoveryDetailItemsCell.titleLabel.text = object?.objectForKey("exerciseName") as? String
discoveryDetailItemsCell.titleLabel.textColor = UIColor.whiteColor()
discoveryDetailItemsCell.durationAndSetsLabel.text = "\((object?.objectForKey("durationOrSets"))!)"
discoveryDetailItemsCell.minAndSetLabel.text = "mins"
...
return discoveryDetailItemsCell
}
There may be similar questions out there but I have not found anything that answers this or I am probably not seeing my mistake clearly.
Thanks for the help in advance. :)
for query with pointer U have to use query like that
let discoveryQuery = PFQuery(className: "DiscoveryDetails")
discoveryQuery.cachePolicy = .CacheElseNetwork
discoveryQuery.whereKey("discoveryID", equalTo: PFObject(withoutDataWithClassName: "Discovery", objectId: "\(varInDDT!.objectId!)"))
discoveryQuery.orderByDescending("createdAt")
return discoveryQuery
for downloading the detail you have to pass at least ID of the object you want to download from the first viewController to the second or you can pass the whole PFObject
DiscoveryTableView
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("showDetail", sender: indexPath)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "showDetail"){ // define the segue Name
let controller = (segue.destinationViewController as! DiscoveryDetailTableViewController)
let row = sender!.row //we know that sender is an NSIndexPath here.
let selectedObj = discoveryObjects[row] // some var where you hold your data
controller.varInDDT = selectedObj
}
}
and define the var of PFObject in detailVC
DiscoveryDetailTableViewController
var varInDDT : PFObject?