Synchronize database of Firebase with Swift 4 language - ios

I am new at swift 4 programming. I try to show some data(actually they are child names) from database of firebase. When view did load, I can take them and show on tableView but it is not working real time. When I delete some data from firebase, I need to refresh(go back and load again) the viewController. If I refresh the tableView with timer, all data append from its end. If I, firstly, delete after append new data to tableView. It is jittering. Is there anyway to synchronize this tableView with firebase, without these problems?
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
ref.keepSynced(true)
let userRef = self.ref.child("Users").child(Username).child("Schedule")
userRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
self.myList.append(key)
self.CourseList.append(key)
self.LessonsTableView.reloadData()
}
// Lessons are taking and after, they are locating under phone's memory!
UserDefaults.standard.set(self.CourseList, forKey: "LessonsArray")
UserDefaults.standard.synchronize()
})
}
//Setting up our table
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = LessonsTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! LessonsTableViewCell
cell.myLabell.text = myList[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let Dummy:String = myList[indexPath.row] as Any as! String
let Selected:String = (Dummy as NSString) as Any as! String
UserDefaults.standard.set(Selected, forKey: "Selected")
self.performSegue(withIdentifier: "NextPage", sender: self)
}

Firebase's task is a network task which normally runs on a background (lower priority) thread. But tableView.reloadData() is an UI task. So you probably need to execute UI related codes by following codes.
DispatchQueue.main.sync {
let snap = child as! DataSnapshot
let key = snap.key
self.myList.append(key)
self.CourseList.append(key)
self.LessonsTableView.reloadData()
}

it is giving an error like this,
let userRef = self.ref.child("Users").child(Username).child("Schedule")
userRef.observeSingleEvent(of: .value, with: { snapshot in
DispatchQueue.main.sync {
let snap = child as! DataSnapshot
let key = snap.key
self.myList.append(key)
self.CourseList.append(key)
self.LessonsTableView.reloadData()
}
UserDefaults.standard.set(self.CourseList, forKey: "LessonsArray")
UserDefaults.standard.synchronize()
})

Related

Retrieve photos from firebase storage SWIFT

I have a problem since yesterday morning but I can't figure it out how can I resolve this issue.
I'm having a table view which is using prototype cells, 2 labels and 1 photo. For the labels I used Firestore and for the picture firebase storage.
The problems is that the only way I know how to retrieve photos from my firebase storage is this code
let storage = Storage.storage()
let storageRef = storage.reference()
let ref = storageRef.child("Mancare/Mancare3.jpg")
testImage.sd_setImage(with: ref)
I want to retrieve the photos into my table view, but I do not know how can I can accomplish that.
This is what im using for retrieving the labels with Firestore . I'll paste only the necessary parts of the code :
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return labels.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableViewTest.dequeueReusableCell(withIdentifier: "CountryTableViewCell", for: indexPath) as! CountryTableViewCell
let storage = Storage.storage()
let storageRef = storage.reference()
let ref = storageRef.child("Mancare/Mancare3.jpg")
let label = labels[indexPath.row]
cell.labelTest.text = label.firstLabel
cell.labelLaba.text = label.secondLabel
return cell
}
func getDatabaseRecords() {
let db = Firestore.firestore()
labels = [] // Empty the array
db.collection("labels").getDocuments { (snapshot, error) in
if let error = error {
print(error)
return
} else {
for document in snapshot!.documents {
let data = document.data()
let newEntry = Labels(
firstLabel: data["firstLabel"] as! String,
secondLabel: data["secondLabel"] as! String)
self.labels
.append(newEntry)
}
}
DispatchQueue.main.async {
self.tableViewTest.reloadData()
}
}
}
This is how I declared the labels:
struct Labels {
let firstLabel: String
let secondLabel: String
}
var labels: [Labels] = []
If someone can help me , ill be forever grateful . Thanks
First, you need to fix your model so it can help you. Add the bucket name to the model like this:
Struct Labels {
let firstLabel: String
let secondLabel: String
let photoKey: String // This will store the bucket name for this `Labels`
}
Now in your getDatabaseRecords change:
let newEntry = Labels(firstLabel: data["firstLabel"] as! String,
secondLabel: data["secondLabel"] as! String),
photoKey: data["photoKey"] as! String) // Added Line
Then in cellForRow:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableViewTest.dequeueReusableCell(withIdentifier: "CountryTableViewCell", for: indexPath) as! CountryTableViewCell
let label = labels[indexPath.row]
let storageRef = Storage.storage().reference()
let photoRef = storageRef.child(label.photoKey)
cell.labelTest.text = label.firstLabel
cell.labelLaba.text = label.secondLabel
cell.imageView.sd_setImage(with: photoRef) // Assuming the image view in your cell is named this
return cell
}
Last, make sure your document structure matches the new Labels Model in the firebase console, and you have images as well in the root of your storage that match with all the photoKeys. Btw, Labels is not a very good model name, I just went with it for consistency

When I delete cell from tableview cells that are not deleted are duplicated SWIFT

I have a tableView which I created with firebase data.
Here is my load func :
var reservationList = [UserModal]()
private func loadposts() {
reservationList = []
activityIndicator.startAnimating()
Database.database().reference().child("BookReservations").observe(.value) { snapshot in
for case let child as DataSnapshot in snapshot.children {
guard let dict = child.value as? [String:Any] else {
print("Error")
self.activityIndicator.stopAnimating()
return
}
let name = dict["name"] as! String
let date = dict["date"] as? String ?? "nil"
let book = dict["bookName"] as? String ?? "nil"
let FullDate = dict["fullDate"] as? String ?? "nil"
let phoneNumber = dict["phoneNumber"] as? String ?? "nil"
let reservations = UserModal(name: name, dateAndTime: date, choosenBook: bookName , phoneNumber: phoneNumber, tamRandevu: fullDate)
self.reservationList.append(reservations)
print(self.reservationList)
self.activityIndicator.stopAnimating()
self.tableView.reloadData()
}
}
}
extension ReservationListViewController: UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return reservationList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reservationCell", for: indexPath) as! reservationCell
cell.bookNameLabel.text = reservationList[indexPath.row].choosenBook
cell.nameLabel.text = reservationList[indexPath.row].userName
cell.dateAndTimeLabel.text = reservationList[indexPath.row].fullDate
cell.phoneButton.setTitle(reservationList[indexPath.row].phoneNumber, for: .normal)
return cell
}
I can get value and show on tableView. But when I try to delete cell :
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
print("Deleted")
activityIndicator.startAnimating()
let reservationTime = reservationList[indexPath.row].fullDate ?? "nil"
print(reservationTime)
if reservationTime != "nil" {
let stationsRef = database.child("BookReservations")
print(reservationList.count)
print("Before deleting Count of reservation list : \(reservationList.count)")
reservationList.forEach { (UserModal) in
print(UserModal.name)
}
stationsRef.child(reservationTime).setValue(nil)
reservationList.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
print("After deleting Count of reservation list : \(reservationList.count)")
reservationList.forEach { (UserModal) in
print(UserModal.name)
}
activityIndicator.stopAnimating()
}else{
activityIndicator.stopAnimating()
print("no reservation found.")
}
}
}
I can succesfully delete data from firebase real time database. But when I delete data , If there is more than one data in the tableView, the non-deleted data will repeat itself.
I try to check my array count it seems good when delete count is decreasing.. I try tableview reload, begin updates and and updates but nothing works. Only works when I refresh tableview with refresh method. This is my print outputs :
Before deleting Count of reservation list : 2
Optional("yakalaaa")
Optional("xyzzz")
After deleting Count of reservation list : 1
Optional("yakalaaa")
this is my tableView which I load with firebase data :
enter image description here
I delete second cell :
enter image description here
and this is the problem :
enter image description here
any suggestions ?
the problem is with these lines:
tableView.beginUpdates()
reservationList.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .bottom)
tableView.endUpdates()
just use instead
reservationList.remove(at: indexPath.row)
tableView.reloadData()

What am I doing wrong while populating this UITableView in Swift?

I am trying to populate a UITableView using an array and I am unable to do so. Here is what I have so far. This code is for retrieving data and storing it in the array that I am using to populate the UITableView:
func prepareForRetrieval() {
Database.database().reference().child("UserCart").child(Auth.auth().currentUser!.uid).observe(.value, with: {
(snapshot) in
for snap in snapshot.children.allObjects {
let id = snap as! DataSnapshot
self.keyArray.append(id.key)
}
self.updateCart()
})
}
func updateCart() {
for key in keyArray {
Database.database().reference().child("UserCart").child(Auth.auth().currentUser!.uid).child(key).observeSingleEvent(of: .value, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
let itemName = value?["Item Name"] as! String
let itemPrice = value?["Item Price"] as! Float
let itemQuantity = value?["Item Quantity"] as! Int
self.cartArray.append(CartData(itemName: itemName, itemQuantity: itemQuantity, itemPriceNumber: itemPrice))
print(self.cartArray.count)
})
}
}
The data is properly appending into the array and when I print the count of the array, it prints the correct count. This means that the data is there. However, when I try to populate a UITableView, it doesn't detect any data. I have the following code to make sure that there is data in the array before trying to populate the UITableView:
override func viewDidLoad() {
super.viewDidLoad()
cartBrain.prepareForRetrieval()
if cartBrain.cartArray.isEmpty == false{
tableViewOutlet.dataSource = self
tableViewOutlet.reloadData()
}
else {
tableViewOutlet.isHidden = true
tableViewOutlet.isUserInteractionEnabled = false
purchaseButtonOutlet.isEnabled = false
cartEmptyLabel.text = "Your cart is empty. Please add items and check back later."
}
}
When I open the View Controller, the TableView is disabled because it doesn't detect any data. I have already set the data source to self and the thing is that when the count of the array is printed, it again prints the correct amount. I have already set the data source to self for the UITableView. Here is my code for the UITableView:
extension CartViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cartBrain.cartArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cartcustomcell", for: indexPath)
cell.textLabel?.text = cartBrain.cartArray[indexPath.row].itemName
cell.detailTextLabel?.text = String(cartBrain.cartArray[indexPath.row].itemQuantity)
return cell
}
}
I don't understand why the count of the array prints the correct amount meaning that there is data stored in it but when the View Controller is loaded, it detects that the array is empty. Thanks for the help and I'm sorry if the question is a bit unclear.
After appending data to cartArray in updateCart you should reloadData(), like this:
weak var tableViewOutlet: UITableView?
func updateCart() {
for key in keyArray {
Database.database().reference().child("UserCart").child(Auth.auth().currentUser!.uid).child(key).observeSingleEvent(of: .value, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
let itemName = value?["Item Name"] as! String
let itemPrice = value?["Item Price"] as! Float
let itemQuantity = value?["Item Quantity"] as! Int
self.cartArray.append(CartData(itemName: itemName, itemQuantity: itemQuantity, itemPriceNumber: itemPrice))
DispatchQueue.main.async {
self.tableViewOutlet.reloadData()
}
})
}
}
The updateCart doesn't seem to have any connection to the tableViewOutlet so you need to pass in a reference to it in your viewDidLoad like this:
override func viewDidLoad() {
super.viewDidLoad()
cartBrain.tableViewOutlet = tableViewOutlet
cartBrain.prepareForRetrieval()
Note: Since you're using a for loop to trigger the async call multiple times you can use the array count to check if all the items are appended to do the reload to avoid multiple reloads.

How to observe more levels from realtime-database Firebase?

My code do not add values from a while statement of a UIViewController to an Array of a UITableViewController.
This is for a getter function to allow me to see all childrens values under other childrens. Now I'm going to be more specific:
My database node is made of:
Cars -> 0, 1, 2, 3, ... -> Model, Price, ... -> String
As you can see, The number of childs is undefined, so I have to use this control method:
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
}
First of all, In a loading ViewController, I get code node keys of cars and save them to cars variable of type NSMutableArray of the TableViewController. Then I will do the same thing in the TableViewController to get all indexpath.row childrens value.
let rootRef = Database.database().reference()
let carconditionalRef = rootRef.child("Cars")
carconditionalRef.observe(.value) {(snap: DataSnapshot) in
//Get all the children from snapshot you got back from Firebase
let snapshotChildren = snap.children
//Loop over all children (code) in Firebase
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
let carvc = Cars_Table();
carvc.cars.add(child.key)
}
}
It results that with this code I still have empty NSMutableArray. How can I solve this?
Edit 1
I fixed that snippet to this:
import UIKit
import FirebaseDatabase
class Loading: UIViewController {
#IBOutlet weak var loading: UIActivityIndicatorView!
var mother: NSMutableArray = []
override func viewDidLoad() {
super.viewDidLoad()
start()
}
func start() {
loading.startAnimating()
if #available(iOS 10.0, *) {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (timer) in
//let's dance
self.loading.startAnimating()
//call data from database
let rootRef = Database.database().reference()
let conditionalRef = rootRef.child("Cars")
conditionalRef.observe(.value) {(snap: DataSnapshot) in
// Get all the children from snapshot you got back from Firebase
let snapshotChildren = snap.children
// Loop over all children (code) in Firebase
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
self.mother.add(child.key)
}
self.move()
self.loading.stopAnimating()
self.performSegue(withIdentifier: "loadingfinish", sender: nil)
}
}
} else {
// Fallback on earlier versions
}
}
func move() {
let vc = Cars_Table()
vc.cars = self.mother
}
}
Edit 2
I tried using the recursive method, but it did not work. So I tried one more time with the iterative method this time using the while statement.
Here my new function, this time directly in the Car_TableView.swift:
func loadData() {
//call data from database
let rootRef = Database.database().reference()
let conditionalRef = rootRef.child("Cars")
conditionalRef.observe(.value) {(snap: DataSnapshot) in
// Get all the children from snapshot you got back from Firebase
let snapshotChildren = snap.children
// Loop over all children (code) in Firebase
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
self.populateTable.append(child.key)
}
var counter = 0
while counter > -self.populateTable.count {
counter -= 1
let rootRef = Database.database().reference()
let userRef = rootRef.child("Cars").child("\(self.populateTable.count+counter)")
userRef.observeSingleEvent(of: .value, with: { snapshot in
let userDict = snapshot.value as! [String: Any]
let model1 = userDict["Model"] as! String
self.model.add(model1)
let detail1 = userDict["Detail"] as! String
self.detailpage.add(detail1)
let year1 = userDict["Year"] as! String
self.year.add(year1)
let carPrice1 = userDict["Price"] as! String
self.price.add(carPrice1)
let carimageURL1 = userDict["imageURL"] as! String
self.imagePathString.add(carimageURL1)
}) //end observeSingleEvent
}
}
}
When I go to do the while, the observeSingleEvent will be work, but it will repeat n^2 times. Why does this happen?
Edit 3
Since the problem seems to be changed since the start, I edited to give all the relevant details. So, the problem now is different and now are two:
When I load database I have n^2 repeated instruction
To see the table filled with database data, I have to touch the tab bar button to the next ViewController then touch the tab bar button to come back on Car_TableView.swift
For the first problem... onestly I have no idea why this happens 😅
For the second problem I thought to use SVProgressHUD to reload data but it doesn't work on loadData() function and if I try the Instance Method tableView.reloadData() it crashes.
variables are all NSMutableArray since that I have to load a lot of stuff that can change in the time
My viewDidLoad() function is very easy as you can see:
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
This is my Table view data source in our Car_TableView.swift:
override func numberOfSections(in tableView: UITableView) -> Int {
return populateTable.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return populateTable.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "carTableCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! Car_Cell
cell.carLabel?.text = "\(self.model[indexPath.row])"
cell.carSubtitle?.text = "Year: \(self.year[indexPath.row]) - Price: \(self.price[indexPath.row])$"
Alamofire.request("\(self.imagePathString[indexPath.row])").response { response in
guard let image = UIImage(data:response.data!) else {
// Handle error
return
}
let imageData = image.jpegData(compressionQuality: 1.0)
cell.carImage.contentMode = .scaleAspectFit
cell.carImage.image = UIImage(data : imageData!)
}
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowcarDetails" {
let myIndexPath = self.tableView.indexPathForSelectedRow!
//save detail page url in UserDefault
let SVDetail = self.detailpage[myIndexPath.row]
let SVDetaildefaults = UserDefaults.standard
SVDetaildefaults.set(SVDetail, forKey: "sv_detail")
SVDetaildefaults.synchronize()
_ = segue.destination
as! Car_Detail
}
}
//SET CELLS SIZE
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch indexPath.row {
case 0,1,2,3,4:
return 100
default:
return 100
}
}
I also re-post the loadData() function because I simplified the operation in an only while statement:
func loadData() {
//call data from database
let rootRef = Database.database().reference()
let conditionalRef = rootRef.child("Cars")
conditionalRef.observe(.value) {(snap: DataSnapshot) in
// Get all the children from snapshot you got back from Firebase
let snapshotChildren = snap.children
// Loop over all children (code) in Firebase
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
self. populateTable.append(child.key)
let userRef = rootRef.child("Cars").child("\(child.key)")
userRef.observeSingleEvent(of: .value, with: { snapshot in
let userDict = snapshot.value as! [String: Any]
let address1 = userDict["Address"] as! String
self.address.add(address1)
let detail1 = userDict["Detail"] as! String
self.detailpage.add(detail1)
let carnumberOfRooms1 = userDict["numberOfRooms"] as! String
self.numberOfRooms.add(carnumberOfRooms1)
let carPrice1 = userDict["Price"] as! String
self.price.add(carPrice1)
let carimageURL1 = userDict["imageURL"] as! String
self.imagePathString.add(carimageURL1)
}) //end observeSingleEvent
} //end while
} //end snap
}//end func

ios - Setting value in to FIRDataSnapshot

I'm new to swift and having trouble with setting value to FIRDataSnapshot. I simply created a calculator. I took MealCaloryArray in didload method and display it tableviewcell (with the help of FIRDataSnapshot list) and the calory is changed in the plus function and I have to send the new value to tableviewcell again. However, I couldnt set the new value in FIRDataSnapshot array list. I tried to useself.calory[buttonRow].setValue(<value: n, forUndefinedKey:"")but I dont have proper "forUndefinedKey" value. Do you have any suggestions?
My nested Firebase DB Structure and code blocks is attached.
Firebase Child Structure:
Database Screenshot
var calory: [FIRDataSnapshot]! = []
override func viewDidLoad() {
ref = FIRDatabase.database().reference()
let CoursesRef = ref.child("CompanyMeals")
CoursesRef.observe(.childAdded, with: { snapshot in
self.calory = snapshot.childSnapshot(forPath: "MealCaloryArray").children.allObjects as! [FIRDataSnapshot]
self.calory.append(snapshot)
self.ingredientTableView.reloadData()
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ingredientTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MealCalculatorListCell
let cellcalory = self.calory[indexPath.row].value as? Double
if (cellcalory != nil) {
cell.itemTotalCalory.text = ("\(cellcalory!)")//String (describing: cellcalory)
let cellcalory1 = Int(cellcalory!)
firstCaloriesArray.append((cellcalory1 as AnyObject))
}
}
#IBAction func plusAction(sender: UIButton) {
cell.itemTotalCalory.text = String ((Int(oldcalory!) + fcCalory))
let newcalory = cell.itemTotalCalory.text
let n = String(newcalory!)
self.calory[buttonRow].setValue(value: n, forUndefinedKey: "")
}

Resources