I'm creating an iOS application that uses placenames and rating for each place. I have already made the thing work. I mean that, I save data to my database and I also can read them. The only problem, is when I read them I want them to load on my tableviewcell, by calculating average for each place. See the screenshots and if you don't understand something, ask me to edit the answer.
TableView
Firebase
My Code that loads data to tableview
import UIKit
import FirebaseDatabase
class PlacesTableViewController: UITableViewController {
//MARK: Properties
#IBOutlet weak var placesTableView: UITableView!
var dbRef:FIRDatabaseReference?
var places = [Places]()
private var loadedLabels = [String: String]()
private var loadedRatings = [String: Int]()
override func viewDidLoad()
{
super.viewDidLoad()
dbRef = FIRDatabase.database().reference()
// Loads data to cell.
loadData()
}
override func numberOfSections(in tableView: UITableView) -> Int
{
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
//return the number of rows
return places.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
// Table view cells are reused and should be dequeued using a cell identifier.
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? PlacesTableViewCell else {
fatalError("The dequeued cell is not an instance of PlacesTableView Cell.")
}
let place = places[indexPath.row]
cell.placeLabel.text = place.name
cell.ratingControl.rating = place.rating
return cell
}
private func loadData()
{
dbRef!.observe(.childAdded, with: {
(placeSnapshot) in
//print("Adding place \(placeSnapshot.key)...")
let labels = placeSnapshot.childSnapshot(forPath: "placeLabel")
for (key, label) in labels.value as! [String: String] {
self.updatePlace(key, label: label)
}
let ratings = placeSnapshot.childSnapshot(forPath: "rating")
for (key, rating) in ratings.value as! [String: Int] {
self.updatePlace(key, rating: rating)
}
})
}
private func updatePlace(_ key: String, label: String? = nil, rating: Int? = nil)
{
if let label = label {
loadedLabels[key] = label
}
if let rating = rating {
loadedRatings[key] = rating
}
guard let label = loadedLabels[key], let rating = loadedRatings[key] else {
return
}
if let place = Places(name: label, rating: rating) {
places.append(place)
placesTableView.reloadData()
}
}
}
Places swift
import UIKit
class Places {
//MARK: Properties
var name: String
var rating: Int
//MARK:Types
struct PropertyKey {
static let name = "name"
static let rating = "rating"
}
//MARK: Initialization
init?(name: String, rating: Int) {
// Initialize stored properties.
self.name = name
self.rating = rating
// Initialization should fail if there is no name or if the rating is negative.
// The name must not be empty
guard !name.isEmpty else {
return nil
}
// The rating must be between 0 and 5 inclusively
guard (rating >= 0) && (rating <= 5) else {
return nil
}
}
}
As i understood you want to have a rounded double for your application and not a double. Just change the code inside your loadData() function and it would work for you. Also you will call updatePlace() as you did. Please approve Jay's answer, he wrote the code.
private func loadData()
{
dbRef!.observe(.childAdded, with: {
(placeSnapshot) in
let parentRef = self.dbRef?.child(placeSnapshot.key)
let ratingRef = parentRef?.child("rating")
ratingRef?.observe(.value, with: { snapshot in
let count = snapshot.childrenCount
var total: Double = 0.0
for child in snapshot.children {
let snap = child as! FIRDataSnapshot
let val = snap.value as! Double
total += val
}
let average = total/Double(count)
print("Average for \(placeSnapshot.key) = \(Int(round(average)))")
self.updatePlace("" , label: placeSnapshot.key, rating: Int(round(average)))
})
})
}
This is a pretty verbose answer but it will iterate over the child nodes of the rating node and calculate the average
let parentRef = self.dbRef.child("Paradosiako - Panorama")
let ratingRef = parentRef.child("rating") // rating node
ratingRef.observe(.value, with: { snapshot in
let count = snapshot.childrenCount
var total: Double = 0.0
for child in snapshot.children {
let snap = child as! FIRDataSnapshot
let val = snap.value as! Double
total += val
}
let average = total/Double(count)
print(average)
})
EDIT
To iterate over all the places and get the averages, here's the code
let placesRef = self.dbRef.child("akapnapp")
placesRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let placeSnap = child as! FIRDataSnapshot
let ratingsSnap = placeSnap.childSnapshot(forPath: "rating")
let count = ratingsSnap.childrenCount
var total: Double = 0.0
for child in ratingsSnap.children {
print(child)
let snap = child as! FIRDataSnapshot
let val = snap.value as! Double
total += val
}
let average = total/Double(count)
print(average)
}
})
and the output showing the code works correctly. My child nodes were named a,b,c instead of the childByAutoId in the question but it works either way.
Snap (a) 3
Snap (b) 3
3.0
Snap (a) 2
Snap (b) 4
Snap (c) 5
3.67
Related
Can someone tell me which part of my code is not correct?
There two two files CompDftVC.swift and SelectDftVC.swift, when clock at tableview (CompDftVC) which segue to another tableview (SelectDftVC), which I make some selection and click 'Save' to segue back, then I got duplicate records at the initial tableview. Please see my code and screen shots as attached. Thank you for your help.
// CompDftVC.swift
import UIKit
import Firebase
class CompDftVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return compNDftLst.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = compDftTV.dequeueReusableCell(withIdentifier: "compDftTVCell", for: indexPath)
cell.textLabel?.text = compNDftLst[indexPath.row].compName
if indexPath.row % 2 == 0 {
cell.contentView.backgroundColor = .white
} else {
cell.contentView.backgroundColor = .white
}
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// pass compID to destination
tmpComponentId = compNDftLst[indexPath.row].compId!
// animate effect
compDftTV.deselectRow(at: indexPath, animated: true)
// segue triggered
performSegue(withIdentifier: "segueToSelectDft", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueToSelectDft" {
let svc = segue.destination as! SelectDftVC
svc.passInCompId = tmpComponentId
svc.passInParentEmpty = tmpParentEmpty
}
}
#IBOutlet weak var compDftTV: UITableView!
let node = currCompID+"CompDft"
let compNode = currCompID+"Comp"
let dftNode = currCompID+"Dft"
var compNDftLst = [CompNDft]()
var databaseHandle: DatabaseHandle?
var compRef: DatabaseReference!
var tmpComponentId = String()
var tmpParentEmpty = Int()
override func viewDidLoad() {
super.viewDidLoad()
compDftTV.delegate = self
compDftTV.dataSource = self
self.compNDftLst.removeAll()
// check whether the compDft table is empty
// if empty only return component with section (one row)
// if not, prepare tmp Array to display on tableview
compRef = Database.database().reference().child(node)
compRef.observe(DataEventType.value, with: { (snapshot) in
// need to consider the workshop is still empty or not?
if snapshot.childrenCount > 0 {
// CompDft table is not empty. need to retrieve comp/defect list and present them at an expandable tableview
// self.compNDftLst.removeAll()
// component already has defects. prepare the tablewview with defect list
self.tmpParentEmpty = 0
self.compRef = Database.database().reference().child(self.compNode)
self.compRef.observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
if snapshot.childrenCount > 0 {
for comp in snapshot.children.allObjects as! [DataSnapshot] {
let compObj = comp.value as? [String: AnyObject]
self.tmpComponentId = comp.key
let componentName = compObj?["name"]
let component = CompNDft(compId: self.tmpComponentId as String?, compName: componentName as! String?, dftList: [""])
self.compNDftLst.append(component)
}
self.compDftTV.reloadData()
}
})
} else {
// CompDft table is empty. Only need to get componment list from Comp table
self.tmpParentEmpty = 1
self.compRef = Database.database().reference().child(self.compNode)
self.compRef.observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
if snapshot.childrenCount > 0 {
// self.compNDftLst.removeAll()
for comp in snapshot.children.allObjects as! [DataSnapshot] {
let compObj = comp.value as? [String: AnyObject]
self.tmpComponentId = comp.key
let componentName = compObj?["name"]
let component = CompNDft(compId: self.tmpComponentId as String?, compName: componentName as! String?, dftList: [""])
self.compNDftLst.append(component)
}
self.compDftTV.reloadData()
}
})
}
})
} // ViewDidLoad
} // class
// SelectDftVC.swift
import UIKit
import Firebase
class SelectDftVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tmpDisplayDefectNameList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = selectDftTV.dequeueReusableCell(withIdentifier: "dftListTVCell", for: indexPath)
cell.textLabel?.text = tmpDisplayDefectNameList[indexPath.row]
// get the componet ID and check which defect was selected and then mark them .checkmark
if tmpDefectSelected[indexPath.row] == 1 {
cell.accessoryType = .checkmark
}
if indexPath.row % 2 == 0 {
cell.contentView.backgroundColor = .white
} else {
cell.contentView.backgroundColor = .white
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCell.AccessoryType.checkmark {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.none
tmpDefectSelected[indexPath.row] = 0
} else {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.checkmark
tmpDefectSelected[indexPath.row] = 1
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
let node = currCompID+"CompDft"
let compNode = currCompID+"Comp"
let dftNode = currCompID+"Dft"
var passInCompId = String()
var passInParentEmpty = Int()
var ref: DatabaseReference!
var ref2: DatabaseReference!
var ref3: DatabaseReference!
var ref4: DatabaseReference!
var ref5: DatabaseReference!
var ref6: DatabaseReference!
var refDft: DatabaseReference!
var selectedDft: DatabaseReference!
var selectedDftId = [String]()
var tmpDefectSelected = [Int]()
var initialTmpDefectSelected = [Int]()
var tmpDisplayDefectIdList = [String]()
var tmpDisplayDefectNameList = [String]()
var tmpSelectedDftID = [String]()
var tmpSelectedDftName = [String]()
var hasData = Int()
#IBOutlet weak var selectDftTV: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
selectDftTV.delegate = self
selectDftTV.dataSource = self
tmpDisplayDefectIdList.removeAll() // defect ID
tmpDisplayDefectNameList.removeAll() // defect Name
selectedDftId.removeAll() // Selected defect ID
tmpDefectSelected.removeAll() // Integer array for checkmark
initialTmpDefectSelected.removeAll() // Initial Integer array for checkmark (to be updated when populating tableview
if passInParentEmpty == 1 {
// component doesn't contain any defect
ref = Database.database().reference().child(dftNode)
ref.observe(DataEventType.value, with: { (snapshot) in
// need to consider the workshop is still empty or not?
if snapshot.childrenCount > 0 {
// get defect list
for defect in snapshot.children.allObjects as! [DataSnapshot] {
let defectObj = defect.value as? [String: AnyObject]
let defectId = defect.key
let defectName = defectObj?["name"]
self.tmpDisplayDefectIdList.append(defectId)
self.tmpDisplayDefectNameList.append(defectName as! String)
self.tmpDefectSelected.append(0)
self.initialTmpDefectSelected.append(0)
}
self.prepareSelectedDftList()
}
})
} else {
// the component already contain defect
ref = Database.database().reference().child(dftNode)
ref.observe(DataEventType.value, with: { (snapshot) in
// need to consider the workshop is still empty or not?
if snapshot.childrenCount > 0 {
// get defect list
for defect in snapshot.children.allObjects as! [DataSnapshot] {
let defectObj = defect.value as? [String: AnyObject]
let defectId = defect.key
let defectName = defectObj?["name"]
self.tmpDisplayDefectIdList.append(defectId)
self.tmpDisplayDefectNameList.append(defectName as! String)
self.tmpDefectSelected.append(0)
self.initialTmpDefectSelected.append(0)
}
}
self.ref2 = Database.database().reference().child(self.node)
self.ref2.queryOrderedByKey().queryEqual(toValue: self.passInCompId).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.childrenCount > 0 {
self.hasData = 1
} else {
self.hasData = 0
}
if self.hasData == 1 {
self.ref3 = Database.database().reference().child(self.node)
self.ref3.queryOrderedByKey().queryEqual(toValue: self.passInCompId).observe(.childAdded, with: { (snapshot) in
if snapshot.childrenCount > 0 {
// selected component has defects
for child in snapshot.children {
let snap = child as! DataSnapshot
let tmpkey = snap.key as String?
self.selectedDftId.append(tmpkey!)
}
}
self.prepareSelectedDftList()
})
} else {
self.prepareSelectedDftList()
}
})
// self.ref2.removeAllObservers()
}) // first DBquery
// self.ref.removeAllObservers()
}
} // viewDidLoad
#IBAction func saveSelectedDft(_ sender: Any) {
// prepare the array of defects to be marked for component which can override the current one
if tmpDisplayDefectNameList.count > 0 {
for i in 0...tmpDisplayDefectNameList.count - 1 {
if tmpDefectSelected[i] == 1 {
// prepare selected defect ID and name array
tmpSelectedDftID.append(tmpDisplayDefectIdList[i])
tmpSelectedDftName.append(tmpDisplayDefectNameList[i])
}
}
markUsed4CompNDft()
// refDft.removeAllObservers()
}
// segue back and refresh tmp array (may be) and tableview
self.navigationController?.popViewController(animated: true)
} // saveSelectedDftBtn
func prepareSelectedDftList() {
if selectedDftId.count == 0 {
selectDftTV.reloadData()
} else if selectedDftId.count == 1 {
for i in 0...tmpDisplayDefectIdList.count - 1 {
if tmpDisplayDefectIdList[i] == selectedDftId[0] {
tmpDefectSelected[i] = 1
initialTmpDefectSelected[i] = 1
}
}
selectDftTV.reloadData()
} else if selectedDftId.count > 1 {
for i in 0...tmpDisplayDefectIdList.count - 1 {
for j in 0...selectedDftId.count - 1 {
if tmpDisplayDefectIdList[i] == selectedDftId[j] {
tmpDefectSelected[i] = 1
initialTmpDefectSelected[i] = 1
}
}
}
// self.ref.removeAllObservers()
selectDftTV.reloadData()
}
} // prepareSelectedDftList
func resetComponentUsedToZero() {
var clearCompId = String()
var clearCompName = String()
ref5 = Database.database().reference()
ref5.child(compNode).queryOrderedByKey().queryEqual(toValue: passInCompId).observe(.childAdded, with: { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
if key == "id" {
clearCompId = snap.value as! String
}
if key == "name" {
clearCompName = snap.value as! String
}
}
let updatedComponent = [
"id" : clearCompId,
"name" : clearCompName,
"date" : utcDate(),
"time" : utcTime(),
"updated" : currUID,
"used" : 0
] as [String: Any]
self.ref5.child(self.compNode).child(self.passInCompId).setValue(updatedComponent)
})
ref5.removeAllObservers()
}
func setComponentUsedToOne() {
var clearCompId = String()
var clearCompName = String()
ref5 = Database.database().reference()
ref5.child(compNode).queryOrderedByKey().queryEqual(toValue: passInCompId).observe(.childAdded, with: { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
if key == "id" {
clearCompId = snap.value as! String
}
if key == "name" {
clearCompName = snap.value as! String
}
}
let updatedComponent = [
"id" : clearCompId,
"name" : clearCompName,
"date" : utcDate(),
"time" : utcTime(),
"updated" : currUID,
"used" : 1
] as [String: Any]
self.ref5.child(self.compNode).child(self.passInCompId).setValue(updatedComponent)
})
ref5.removeAllObservers()
}
func markUsed4CompNDft() {
ref6 = Database.database().reference()
// check any changes made
if initialTmpDefectSelected != tmpDefectSelected {
if tmpSelectedDftID.count == 0 {
// clear all defect
ref6.child(node).child(passInCompId).setValue(nil)
// should change component 'used' to 0
resetComponentUsedToZero()
} else {
ref6.child(node).child(passInCompId).setValue(nil)
for i in 0...tmpSelectedDftID.count - 1 {
ref6.child(node).child(passInCompId).child(tmpSelectedDftID[i]).setValue(tmpSelectedDftName[i])
}
// mark component used = 1
setComponentUsedToOne()
// mark defect(s) used = 1
if tmpSelectedDftID.count > 0 {
// ref = Database.database().reference().child(dftNode)
ref4 = Database.database().reference().child(dftNode)
for i in 0...tmpSelectedDftID.count - 1 {
ref4.queryOrderedByKey().queryEqual(toValue: tmpSelectedDftID[i]).observe(.childAdded, with: { (sp) in
self.ref4.child(self.tmpSelectedDftID[i]).updateChildValues(["date" : utcDate()])
self.ref4.child(self.tmpSelectedDftID[i]).updateChildValues(["time" : utcTime()])
self.ref4.child(self.tmpSelectedDftID[i]).updateChildValues(["updated" : currUID])
self.ref4.child(self.tmpSelectedDftID[i]).updateChildValues(["used" : 1])
})
}
}
// ref4.removeAllObservers()
}
}
} // markUsed4CompNDft
} // class
I'm having trouble displaying all of the followers of user on a table view cell with their profile picture and full name (similar to instagram).
A snippet of my firebase JSON structure is:
"followers" : {
"FoFQDAGGX9hntBiBdXYCBHd8yas2" : {
"CjeP35ceAQZJuUPhm7U1eF3Yq4F3" : true,
"FjS4wUpXAUa5aWwXkjvujHxE4He2" : true,
"Gmg1ojNoBiedFPRNSL4sBZz2gSx2" : true,
"PqMkClaPM3W8k7ZSgzAHb3yne5D3" : true,
"buS4recuDpdg60ckFqwjoU344TC2" : true
},
"users" : {
"CjeP35ceAQZJuUPhm7U1eF3Yq4F3" : {
"email" : "bbbb#gmail.com",
"fullname" : "Bbbb",
"profileImageUrl" : "https://firebasestorage.googleapis.com/v0/b/pinion-4896b.appspot.com/o/profile_image%2FCjeP35ceAQZJuUPhm7U1eF3Yq4F3?alt=media&token=0449c633-b397-4452-b2df-41f3a5390084",
"work" : "Nottingham",
},
Code in the table view cell (FollowersTableViewCell):
#IBOutlet weak var followersProfileImage: UIImageView!
#IBOutlet weak var followersNameLabel: UILabel!
var user: UserModel? {
didSet {
updateView()
}
}
func updateView() {
followersNameLabel.text = user?.fullname
if let photoUrlString = user?.profileImageUrl {
let photoUrl = URL(string: photoUrlString)
followersProfileImage.sd_setImage(with: photoUrl, placeholderImage: UIImage(named: "placeholderImg"))
}
}
EDIT:
Code in view controller (FollowersViewController)
#IBOutlet weak var tableView: UITableView!
var users: [UserModel] = []
func loadusers() {
let ref = Database.database().reference()
guard let currentUser = Auth.auth().currentUser?.uid else { return }
var followersNames = [String]()
var profileImage = [String]()
let followersRef = ref.child("followers").child(currentUser) //retreives all nodes in the following node
followersRef.observe(DataEventType.value, with: { snapshot in
print(snapshot.children.allObjects)
for child in snapshot.children { //build the array of keys
let snap = child as! DataSnapshot
let key = snap.key
let userRef = ref.child("users").child(key) //get the user name and profile image from the users node
userRef.observeSingleEvent(of: .value, with: { snapshot in
let followersName = snapshot.childSnapshot(forPath: "fullname").value as! String
let followersProfileImageUrl = snapshot.childSnapshot(forPath: "profileImageUrl").value as! String
print(followersName)
print(followersProfileImageUrl)
followersNames.append(followersName)
profileImage.append(followersProfileImageUrl)
self.tableView.reloadData()
})
}
})
}
extension FollowersViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FollowersTableViewCell", for: indexPath) as! FollowersTableViewCell
let user = users[indexPath.row]
cell.user = user
return cell
}
}
Now the code runs and the profile picture and fullname of the followers are printed on the console but doesn't show anything on the table view of the app - thanks in advance :)
Update:
User model definition
class UserModel {
var email: String?
var work: String?
var profileImageUrl: String?
var fullname: String?
var id: String?
}
extension UserModel {
static func transformUser(dict: [String: Any], key: String) -> UserModel {
let user = UserModel()
user.email = dict["email"] as? String
user.work = dict["work"] as? String
user.profileImageUrl = dict["profileImageUrl"] as? String
user.fullname = dict["fullname"] as? String
user.id = key
return user
}
}
Your TableView does not display any data because you don't populate users array at any point.
I might want to instantiate an UserModel object in observeSingleEvent implementation, add the object to users array and invoke reloadData (or insertRows) method also right after that. (Instead of outside the implementation block)
As requested, here is a quick (and dirty) way to create an user object and refresh the UI
let user = UserModel()
user.fullname = snapshot.childSnapshot(forPath: "fullname").value as? String
user.profileImageUrl = snapshot.childSnapshot(forPath: "profileImageUrl").value as? String
self.users.append(user)
self.tableView.reloadData()
I've already seen: Swift UITableView reloadData in a closure but it still does not work. That's why I'm creating a new thread for this.
I'm trying to insert Firestore data into a custom tableview. But when I print the numbers it returns (In the console):
"MyDogwalk.listTxt"
And no data is showing up on the tableview.
I guess all of this is relevant. (I also have 2 classes, with init etc)
class HistoryViewController: UIViewController {
//Tableview
#IBOutlet weak var tableView: UITableView!
let db = Firestore.firestore()
var list: [listTxt] = []
override func viewDidLoad()
{
super.viewDidLoad()
list = createArray()
tableView.delegate = self
tableView.dataSource = self
}
func createArray() -> [listTxt]
{
var tempTxt: [listTxt] = []
//Authentication
let authentication = Auth.auth().currentUser?.uid
//Choosing collection
db.collection("rastad").document(authentication!).collection("promenad").getDocuments()
{ (QuerySnapshot, err) in
if err != nil
{
print("Error getting documents: \(String(describing: err))");
}
else
{
//For-loop
for _ in QuerySnapshot!.documents
{
self.list.removeAll()
let document = QuerySnapshot!.documents.first
let data = document!.data()
data.forEach { (item) in
let data1 = data["Dog"] as? String
let data2 = data["Person"] as? String
let data3 = data["What"] as? String
let data4 = data["Date"] as? String
let data5 = data["Time"] as? String
let txt = listTxt(dog: data1!, person: data2!, action: data3!, time: data4!, date: data5!)
print(txt)
tempTxt.append(txt)
}
}
self.tableView.reloadData()
}
}
//return tempTxt
return list
}
}
extension HistoryViewController: UITableViewDelegate, UITableViewDataSource
{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let listPath = list[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ListCell") as! HistoryCell
cell.setCell(list: listPath)
return cell
}
}
And.. Why is this downvoted? I got an answer that was actually working for this case, and the question is detailed enough for people to understand, isn't it?
createArray() method runs async code, and fills tempTxt. But you are returning tempTxt before async code has been run. So instead returning from createArray method and setting its value to self.list, just do it in the method itself:
self.list = tempTxt
self.tableView.reloadData()
You are iterating over documents but always using data of documents.first. Try this:
self.list.removeAll()
for document in QuerySnapshot!.documents {
let data = document!.data()
data.forEach { (item) in
let data1 = data["Dog"] as? String
let data2 = data["Person"] as? String
let data3 = data["What"] as? String
let data4 = data["Date"] as? String
let data5 = data["Time"] as? String
self.list.append(listTxt(dog: data1!, person: data2!, action: data3!, time: data4!, date: data5!))
}
}
self.tableView.reloadData()
Change self.tableView.reloadData() to
self.list = tempTxt
DispatchQueue.main.async {
self.tableView.reloadData()
}
And skip returning array from that func
*I'm fairly new to swift
I'm currently using Swift 4, Xcode 9, and Firebase. My goal is to create an app that stores data in a list, displays it in a table view, and allows the user to add more data to the list. I'm stuck on the displaying data part, I created a function that is supposed to get the data from the database, then add it into an array so that I can display individual parts of it on a custom table view cell. Here's my code:
class OrdersPage: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return orders.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "orderCell", for: indexPath) as! OrderCell
cell.setOrder(order: orders[indexPath.row])
print("Adding new cell")
return cell
}
#IBOutlet weak var tableView: UITableView!
var ref: DatabaseReference!
var orders = [Order]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
self.ref = Database.database().reference()
orders = getOrders()
}
func getOrders() -> [Order] {
var tempArray = [Order]()
ref.child("Orders").observe(.value) { (snapshot) in
for child in snapshot.children {
let orderDB = child as! DataSnapshot
let orderDict = orderDB.value as! [String: Any]
let name = orderDict["name"] as! String
let date = orderDict["date"] as! String
let time = orderDict["time"] as! String
let hotel = orderDict["hotel"] as! String
let room = orderDict["room"] as! String
let airport = orderDict["airport"] as! String
let agent = orderDict["agent"] as! String
let price = orderDict["price"] as! String
//let submitted = orderDict["submitted"] as! String
tempArray.append(Order(name: name, date: date, time: time, hotel: hotel, room: room, airport: airport, agent: agent, price: price))
}
}
return tempArray
}
Based off of my testing, the issue is that the orders array doesn't contain anything when the numberOfRowsInSection is called so it doesn't create any cells in the table view. I'm not sure why it's not working as it should and have been stuck on this for quite some time now, any help is appreciated.
getOrders() is Asynchronous call so you need to reload your table after you got data from server.
Here is the way you can achieve that.
Replace:
func getOrders() -> [Order]
with
func getOrders()
And your getOrders method will look like:
func getOrders() {
ref.child("Orders").observe(.value) { (snapshot) in
for child in snapshot.children {
let orderDB = child as! DataSnapshot
let orderDict = orderDB.value as! [String: Any]
let name = orderDict["name"] as! String
let date = orderDict["date"] as! String
let time = orderDict["time"] as! String
let hotel = orderDict["hotel"] as! String
let room = orderDict["room"] as! String
let airport = orderDict["airport"] as! String
let agent = orderDict["agent"] as! String
let price = orderDict["price"] as! String
//let submitted = orderDict["submitted"] as! String
//Add your data into array
self.orders.append(Order(name: name, date: date, time: time, hotel: hotel, room: room, airport: airport, agent: agent, price: price))
}
//Reload your tableView here
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
I have updated inner code. Check comments.
Now in your viewDidLoad method Replace:
orders = getOrders()
With
getOrders()
You can use didSet during define your variable of self.orders for reloading UITableView
Here your table will automatically reload when any data is assigned to self.orders
Replace your declaration
var orders = [Order]()
with below code
var orders : [Order] = [] {
didSet {
tableView.reloadData()
}
}
Hello I have a tableviewcell where i can populate it with custom data from my pc, but i can't use my firebase data on the cell that i have made. I want to fill my cell with String and Int, not only Strings. My code is:
PlacesTableViewController Class
import UIKit
import FirebaseDatabase
class PlacesTableViewController: UITableViewController {
//MARK: Properties
#IBOutlet weak var placesTableView: UITableView!
//database reference
var dbRef:FIRDatabaseReference?
var places = [Places]()
var myList:[String] = []
//handler
var handle:FIRDatabaseHandle?
override func viewDidLoad() {
super.viewDidLoad()
dbRef = FIRDatabase.database().reference()
// Loads data to cell.
loadData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return places.count
//return myList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "PlacesTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? PlacesTableViewCell else {
fatalError("The dequeued cell is not an instance of PlacesTableView Cell.")
}
let place = places[indexPath.row]
cell.placeLabel.text = place.name
cell.ratingControl.rating = place.rating
//cell.placeLabel.text = myList[indexPath.row]
//cell.ratingControl.rating = myRatings[indexPath.row]
return cell
}
//MARK: Private Methods
private func loadData() {
handle = dbRef?.child("placeLabel").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String
{
self.myList.append(item)
self.placesTableView.reloadData()
print (item)
}
})
/* handle = dbRef?.child("rating").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String
{
self.myList.append(item)
self.placesTableView.reloadData()
}
})*/
/*guard let place1 = Places(name: "Veranda", rating: 4) else {
fatalError("Unable to instantiate place1")
}
places += [place1]*/
}
}
Places Class
import UIKit
class Places {
//MARK: Properties
var name: String
var rating: Int
//MARK:Types
struct PropertyKey {
static let name = "name"
static let rating = "rating"
}
//MARK: Initialization
init?(name: String, rating: Int) {
// Initialize stored properties.
self.name = name
self.rating = rating
// Initialization should fail if there is no name or if the rating is negative.
// The name must not be empty
guard !name.isEmpty else {
return nil
}
// The rating must be between 0 and 5 inclusively
guard (rating >= 0) && (rating <= 5) else {
return nil
}
}
}
PlacesTableViewCell Class
import UIKit
import FirebaseDatabase
class PlacesTableViewCell: UITableViewCell, UITableViewDelegate {
//MARK: Properties
#IBOutlet weak var placeLabel: UILabel!
#IBOutlet weak var ratingControl: RatingControl!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Firebase Database
Assuming your database layout should instead look like this (see comments above):
...
placeLabel
|
-- XXY: "Veranda"
-- YYY: "Dio Con Dio"
rating
|
-- XXX: 4
-- YYY: 1
...
then try this:
private func loadData() {
dbRef!.child("placeLabel").observe(.childAdded) {
(snapshot) in
let label = snapshot.value as! String
self.updatePlace(snapshot.key, label: label)
}
dbRef!.child("rating").observe(.childAdded) {
(snapshot) in
let rating = snapshot.value as! Int
self.updatePlace(snapshot.key, rating: rating)
}
}
private var loadedLabels = [String: String]()
private var loadedRatings = [String: Int]()
private func updatePlace(_ key: String, label: String? = nil, rating: Int? = nil) {
if let label = label {
loadedLabels[key] = label
}
if let rating = rating {
loadedRatings[key] = rating
}
guard let label = loadedLabels[key], let rating = loadedRatings[key] else {
return
}
if let place = Places(name: label, rating: rating) {
places.append(place)
placesTableView.reloadData()
}
}
By the way, you can temporarily hack your database — using Firebase (nice!) web console — if you want to quickly validate the above solution.
Writing to Database. Try the following code to write the nodes in your database (i.e., this code reuses the same key across all place properties):
let key = dbRef!.child("placeLabel").childByAutoId().key
dbRef!.child("placeLabel").child(key).setValue(placeLabel.text)
dbRef!.child("comment").child(key).setValue(commentTextField.text)
dbRef!.child("rating").child(key).setValue(ratingControl.rating)
Hacking the Database. To edit the database manually, try:
open http://console.firebase.google.com
select your app
open database option
add a new node with the right key
delete the old node