firestore to store about more than 500 information and I want to display it to table view. Basically, I have successfully display all the data in my cell, but the problem is, it takes more than 1 minute to load all data. While the data loaded, I cannot scroll the table view, unless all data finish load. How to enable scrolling while the data is still loading? If not possible, how to load first 20 data first, and will continue load if user is at the end of the cell? Here is some code that I have tried to
get data from firestore:
func getData () {
db.collection("fund").getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
for document in querySnapshot!.documents {
let data = document.data()
let agencyPath = data["agensi"] as? String ?? ""
let title = data["title"] as? String ?? ""
let program = data["program"] as? String ?? ""
let perniagaan = data["perniagaan"] as? String ?? ""
let newMax = data["max"] as? Int
let agencyId = document.documentID
let query = Firestore.firestore().collection("Agensi")
let newQuery = query.whereField("name", isEqualTo: "\(agencyPath)")
newQuery.getDocuments()
{
(querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)");
} else
{
for document in querySnapshot!.documents {
let data = document.data()
let logo = data["logo"] as? String ?? ""
//store to Struct
let newModel = DisplayModel(agency: title, agencyId: agencyId, programTag: program, perniagaanTag: perniagaan, max: newMax, agencyPath: agencyPath, logoUrl: logo, agencyTitle: agencyPath)
self.agencyList.append(newModel)
}
self.tableView.reloadData()
self.dismiss(animated: false, completion: nil)
}
}
}
}
}
}
display data on cell:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellData: DisplayModel
if searchController.searchBar.text != "" {
cellData = filteredData[indexPath.row]
} else {
cellData = agencyList[indexPath.row]
}
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? HomeTableViewCell
cell?.agencyName.text = cellData.agency
cell?.agencyImage.sd_setImage(with: URL(string: "\(cellData.logoUrl ?? "")"), placeholderImage: UIImage(named: "no_pic_image"))
return cell!
}
Action on last row of cell:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if searchController.searchBar.text != "" {
let lastElement = filteredData.count - 1
if indexPath.row == lastElement {
//getData()
// handle your logic here to get more items, add it to dataSource and reload tableview
}
} else {
let lastElement = agencyList.count - 1
if indexPath.row == lastElement {
//getData()
// handle your logic here to get more items, add it to dataSource and reload tableview
}
}
}
I really have no idea what method I should do to load 20 data first and continue load at the end of cell row, if there is no solution, at least I could scroll the table view during the load session. Thank You, for your information, i just learn swift last month. Thank you for helping me.
You should definitly adopt the UITableViewDataSourcePrefetching protocol.
Check some blogs, like:
https://www.raywenderlich.com/187041/uitableview-infinite-scrolling-tutorial
and adopt it to pagination as described here:
https://firebase.google.com/docs/firestore/query-data/query-cursors
Related
im trying to delete a cell from a tableview, and from Firestore too.
This how I declared my cart :
struct Cart
{
var photoKeyCart: String
var foodCart: String
var priceCart: Int
}
var cart: [Cart] = [] // This is in the cart controller
This is my tableview where I have my cart items :
extension CartViewController: UITableViewDelegate, UITableViewDataSource
{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var sum = 0
for item in cart{
sum += item.priceCart
}
priceTotalLabel.text = "\(sum) lei"
return cart.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = cartTableView.dequeueReusableCell(withIdentifier: "CartTableViewCell", for: indexPath) as! CartTableViewCell
let carts = cart[indexPath.row]
let storageRef = Storage.storage().reference()
let photoRef = storageRef.child(carts.photoKeyCart)
cell.foodInCartPrice.text = " \(carts.priceCart) lei "
cell.foodInCartName.text = carts.foodCart
cell.foodInCartImage.sd_setImage(with: photoRef)
cell.foodInCartImage.layer.borderWidth = 1
cell.foodInCartImage.layer.masksToBounds = false
cell.foodInCartImage.layer.borderColor = UIColor.black.cgColor
cell.foodInCartImage.layer.cornerRadius = cell.foodInCartImage.frame.height/2
cell.foodInCartImage.clipsToBounds = true
return cell
}
This is how im getting the data from the Firestore into the cart. This is called in the view did load.
func getCartProducts() {
let db = Firestore.firestore()
let userID = (Auth.auth().currentUser?.uid)!
db.collection("CartDatabase").document(userID).collection("CartItems").getDocuments { (document, error) in
if let error = error {
print(error)
return
} else {
for document in document!.documents {
let data = document.data()
let newEntry = Cart(photoKeyCart: data["photoKeyCart"] as! String, foodCart: data["foodCart"] as! String , priceCart: data["priceCart"] as! Int
)
self.cart.append(newEntry)
}
}
DispatchQueue.main.async {
// self.datas = self.filteredData
self.cartTableView.reloadData()
}
}
}
And, this is how im trying to delete the cell from the tableview, and from the Firestore too.
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let carts = cart[indexPath.row]
let storageRef = Storage.storage().reference()
let photoRef = storageRef.child(carts.photoKeyCart)
photoRef.delete { error in
if let error = error {
print(error.localizedDescription)
} else {
print("File deleted successfully")
}
}
let db = Firestore.firestore()
let userID = (Auth.auth().currentUser?.uid)!
db.collection("CartDatabase").document(userID).collection("CartItems").getDocuments { (document, error) in
if let error = error {
print(error.localizedDescription)
} else {
for document in document!.documents {
//print("\(document.documentID) => \(document.data())")
db.collection("CartDatabase").document(userID).collection("CartItems").document(document.documentID).delete()
//self.db.collection("users").document((user?.uid)!).collection("children").document("\(document.documentID)").delete()
}
}}
cart.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
I have the following problem. When im trying to delete the cell, it works, but when im closing the cart and coming back again to the cart, it deletes all the items into the cart, not just the one I tried to delete.
What I want to achieve : to delete just only the cell selected.
Help :D
UPDATE:
I have a tableview with which contains a food, each cell is aa different king of food. I have a plus button, when the plus button is tapped, ill send the datas of the food into the Firestore, and in the cart I retrieve the data.
This is how im sending the data to the cart :
func updateDocument(collection: String, newValueDict: [String : Any], completion:#escaping (Bool) -> Void = {_ in }) {
let db = Firestore.firestore()
let userID = (Auth.auth().currentUser?.uid)!
db.collection(collection).document(userID).collection("CartItems").document().setData(newValueDict, merge: true){ err in
if let err = err {
print("Error writing document: \(err)")
completion(false)
}else{
completion(true)
}
}
}
And when I tapped the cell :
cell.didTapButton = {
self.updateDocument(collection: "CartDatabase",newValueDict: ["foodCart" : mancare.foodName, "photoKeyCart": mancare.photoKeyRestaurant, "priceCart": mancare.priceFood])
}
Check the photos
Photo1
Photo2
Without seeing all of the code it's hard to provide a specific example but let me cover this at a high level.
Suppose we have a posts class object
class PostsClass {
var docId = ""
var post = ""
}
and an class array (aka a 'dataSource') to store them in
var postsArray = [PostsClass]()
The first step is to load all of the posts from Firebase, storing the docId and post text in each class and then store the class in the dataSource array.
myFirebase.getDocuments { doc...
for doc in documents { //iterate over the documents and populate the array
let post = PostClass(populate with data from firebase)
self.postsArray.add(post)
}
}
the dataSouce array will look like this
postsArray[0] = some post
postsArray[1] = another post
etc, and all of that is displayed in a tableView.
The user then decides to delete the post at row 1. So they swipe row one, which fires a tableView delegate event letting the app know the index of the swiped row.
Step 1: You then get that post from the array based on the swiped row index
let postToDelete = self.postsArray[swiped index]
let docIdToDelete = postsToDelete.docID
Step 2: then remove it from the array
self.postsArray.deleteRow(atIndex: swiped index)
Step 3: then delete it from Firebase.
self.my_firebase.collection("posts").document(docIdToDelete).delete {....
Note that the func tableView:tableView:commit editingStyle will present the editing style of .delete when the row is supposed to be deleted and also provide the index in indexPath.row
** I want my tableview to reload after it sees a change in firestore database I thought that using tableview reload would make it reload but no it doesn't it only loads the new data after I restart the app I want the new data to reload right after function load daily motivation has a change in it **
import UIKit
import Firebase
//MARK: MAINVIEW MOTIVATION
class motivationviewcontroller : UIViewController,UITableViewDataSource,UITableViewDelegate{
var motivationThoughts = [MotivatioNDataModel]()
var tableview : UITableView!
override func viewDidLoad() {
print("madicc")
print("the user logged in is \( Auth.auth().currentUser?.email)")
tableview = UITableView(frame: view.bounds, style: .plain)
tableview.backgroundColor = UIColor.white
view.addSubview(tableview)
var layoutGuide : UILayoutGuide!
layoutGuide = view.safeAreaLayoutGuide
let cellNib = UINib(nibName: "dailyMotivationTableViewCell", bundle: nil)
tableview.register(cellNib, forCellReuseIdentifier: "DailyThoughtCELL")
tableview.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
tableview.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
tableview.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
tableview.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
tableview.dataSource = self
tableview.delegate = self
loaddailymotivation()
self.tableview.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
//loaddailymotivation()
self.tableview.reloadData()
}
//======================================================================
//MARK: LOADS THE DATA INTO THE TABLEVIEW
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
motivationThoughts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DailyThoughtCELL", for: indexPath) as? dailyMotivationTableViewCell
cell!.generateCellsforDailymotivation(_MotivationdataMODEL: motivationThoughts[indexPath.row])
return cell!
}
//MARK: FUNCTION THAT HANDLES GETTING THE DATA FROM FIREBASE
func loaddailymotivation() {
FirebaseReferece(.MotivationDAILY).getDocuments { (snapshot, error) in
if let error = error {
print("error getting MOTIVATIONDAILY DATA \(error.localizedDescription)")
}
else {
guard let snapshot = snapshot else { return }
for allDocument in snapshot.documents {
let data = allDocument.data()
print("\(allDocument.documentID) => \(allDocument.data())")
print("we have\(snapshot.documents.count) documents in this array")
let dailymotivationTitle = data["Motivation title"] as! String //calls the data thats heald inside of motivation title in firebase
let dailyMotivationScripture = data["daily motivation scripture"] as! String //calls the data thats heald inside of Motivation script in firebase
let dailyMotivationNumberOfLikes = data["Number of likes in daily motivation post"]as! Int
let newthought = MotivatioNDataModel(RealmotivationTitle: dailymotivationTitle, RealmotivationScrip: dailyMotivationScripture, RealmotivationNumberOfLikes: dailyMotivationNumberOfLikes )
self.motivationThoughts.append(newthought)
}
}
}
}
Problem is you are fetching the data but not reloading your tableView after that, Change your loaddailymotivation() with the below one
func loaddailymotivation() {
FirebaseReferece(.MotivationDAILY)
.addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let data = diff.document.data()
let dailymotivationTitle = data["Motivation title"] as! String //calls the data thats heald inside of motivation title in firebase
let dailyMotivationScripture = data["daily motivation scripture"] as! String //calls the data thats heald inside of Motivation script in firebase
let dailyMotivationNumberOfLikes = data["Number of likes in daily motivation post"]as! Int
let newthought = MotivatioNDataModel(RealmotivationTitle: dailymotivationTitle, RealmotivationScrip: dailyMotivationScripture, RealmotivationNumberOfLikes: dailyMotivationNumberOfLikes )
self.motivationThoughts.append(newthought)
}
if (diff.type == .modified) {
print("Modified data: \(diff.document.data())")
// here you will receive if any change happens in your data add it to your array as you want
}
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
}
}
here i have added listeners to your firestore data so if any new data adds up or any data changes into the database you will receive it in the App & will reflect that changes realtime.
Do one thing follow my comment in the code.
You can try add "addSnapshotListener" to Your "FUNCTION THAT HANDLES GETTING THE DATA FROM FIREBASE".
Let's try add it like this:
func loaddailymotivation() {
FirebaseReferece(.MotivationDAILY).getDocuments.addSnapshotListener { (snapshot, error) in
if let error = error {
print("error getting MOTIVATIONDAILY DATA \(error.localizedDescription)")
}
else {
guard let snapshot = snapshot else { return }
for allDocument in snapshot.documents {
let data = allDocument.data()
print("\(allDocument.documentID) => \(allDocument.data())")
print("we have\(snapshot.documents.count) documents in this array")
let dailymotivationTitle = data["Motivation title"] as! String //calls the data thats heald inside of motivation title in firebase
let dailyMotivationScripture = data["daily motivation scripture"] as! String //calls the data thats heald inside of Motivation script in firebase
let dailyMotivationNumberOfLikes = data["Number of likes in daily motivation post"]as! Int
let newthought = MotivatioNDataModel(RealmotivationTitle: dailymotivationTitle, RealmotivationScrip: dailyMotivationScripture, RealmotivationNumberOfLikes: dailyMotivationNumberOfLikes )
self.motivationThoughts.append(newthought)
}
}
}
How can one display the data from the Firestore Collections to tableView on Swift?
This code runs (see below) but I want to import directly from the Firestore database instead of hardtyping data:
var habits = [Habit(id: "1", author: "Maiuran", text: "heyhey")]
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "habitCell", for: indexPath) as! HabitTableViewCell
cell.set(habit: habits[indexPath.row])
return cell
}
The code below works to print to the console but not sure how to display the
let name = Auth.auth().currentUser?.uid
Firestore.firestore().collection("habits").whereField("author", isEqualTo: name).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())")
}
}
}
This is the database: Firestore Database structure
Thank you so much for your time and advice!
You have to check user: if user == nil you must login user before.
override func viewDidLoad() {
super.viewDidLoad()
let user = Auth.auth().currentUser
if user == nil {
login()
} else {
// processing
}
}
Here is a partial solution (now it posts to a given UITextfield so just need to figure out how to post to tableView):
Firestore.firestore().collection("habits").getDocuments(completion: { (snapshot, error) in
snapshot!.documents.forEach({ (document) in
let nametyname = document.data()["author"]
let street = document.data()["friend"]
self.myTextiFieldi.text = nametyname as! String
print(nametyname)
print(street)
})
})
I have this struct:
struct Info {
var name: String = ""
var number = Int()
}
var infoProvided : [Info] = []
I display desired data in a tableView:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "InfoCell") as! InfoTableViewCell
let name = infoProvided[indexPath.row].name
let number = infoProvided[indexPath.row].number
cell.infoLabelLabel.text = "\(name) with \(number)"
return cell
}
I am trying to write data to firebase like this:
self.ref?.child(gotLokasjon).child(location).child(dateAndTime).updateChildValues(["Signed up:" : infoProvided])
This returns the error:
Cannot store object of type _SwiftValue at 0. Can only store objects of type NSNumber, NSString, NSDictionary, and NSArray.'
How can I write my Struct to Firebase?. I would like to write it equal to how its displayed in the tableView:
cell.infoLabelLabel.text = "\(name) with \(number)"
I haven't understood where you want the upload to happen(before or after they are displayed on tableView) so adjust it to suit your needs.
guard let name = infoProvided.name else {return}
guard let number = infoProvided.number else {return}
let data = [ "name": name, "number": number] as [String : Any]
self.ref?.child(gotLokasjon).child(location).child(dateAndTime).updateChildValues(data, withCompletionBlock: { (error, ref) in
if error != nil{
print(error!)
return
}
print(" Successfully uploaded")
})
After a bit of fiddling I did this:
let infoArray = infoProvided.map { [$0.number, $0.name] }
let items = NSArray(array: infoArray)
Then implemented that in the above solution. This seams to work.
I don't know if this is a good solution?
So I have a collection called "Drafts" which contains multiple documents each with an auto ID. Each document contains the fields "name" and "details". Each document is displayed in a tableViewCell under "nameLabel" and "detailsLabel". What I would like to do is when the user clicks on a button at the top of the screen of the First viewController, a copy of the collection "Drafts" is created and pasted under a new collection name called "Messages". This collection is then referencing the Second viewControllers tableViewCells just like on the First ViewController only this time its being referenced under the collection "Messages". Having done some research I have a vague inclination that the answer uses cloud functions to essentially create a copy of the collection and paste it with a new collection name. However being relatively new to coding and firebase, I have no idea how to do this and don't know if this is the correct solution. Please may someone help, any help is greatly appreciated!! Thanks!
First ViewController
func loadDrafts() {
let userRef = db.collection("Users").document(user!)
let draftsRef = userRef.collection("Drafts")
exercisesRef.getDocuments { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshot = querySnapshot {
for document in snapshot.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let details = data["details"] as? String ?? ""
let newDrafts = DraftMessages(name: name, details: details)
self.array.append(newDrafts)
}
self.tableView.reloadData()
}
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! DraftsCell
cell.nameLabel.text = array[indexPath.row].name
cell.detailsLabel.text = array[indexPath.row].details
return cell
}
#IBAction func goButton(_ sender: UIButton) {
\\ Add code here to create copy of previous collection "Drafts" and paste in new collection "Messages"
}
Second ViewController
func loadData() {
let userRef = db.collection("Users").document(user!)
userRef.collection("Messages").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let data = document.data()
let name = data["name"] as? String ?? ""
let details = data["details"] as? String ?? ""
let newMessages = Messages(name: name, details: details)
self.array.append(newMessages)
}
self.tableView.reloadData()
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MessagesCell
cell.nameLabel.text = array[indexPath.row].name
cell.detailsLabel.text = array[indexPath.row].details
return cell
}
Here is my working solution. Many thanks to Franks for the help!
#IBAction func goButton(_ sender: UIButton) {
let userRef = db.collection("Users").document(user!)
let draftsRef = userRef.collection("Drafts")
draftsRef.getDocuments { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapshot = querySnapshot {
for document in snapshot.documents {
let data = document.data()
let batch = self.db.batch()
let docset = querySnapshot
let messagesRef = userRef.collection("Messages").document()
docset?.documents.forEach {_ in batch.setData(data, forDocument: messagesRef)}
batch.commit(completion: { (error) in
if let error = error {
print("\(error)")
} else {
print("success")
}
})
}
}
}
}
}
Edit for Vaibhav Jhaveri:
This function (hopefully) both duplicates the fetched documents data and the data inside of that documents subcollection. (I have not tested this though)
func duplicate() {
let userRef = db.collection("Users").document(userID)
let batch = self.db.batch()
let draftsRef = userRef.collection("Drafts")
draftsRef.getDocuments { (snapshot, err) in
if let err = err {
print(err.localizedDescription)
return
}
guard let snapshot = snapshot else { return }
snapshot.documents.forEach({ (document) in
let data = document.data()
let messageID = UUID().uuidString
let messagesRef = userRef.collection("Messages").document(messageID)
batch.setData(data, forDocument: messagesRef, merge: true)
let yourSubCollectionRef = draftsRef.document(document.documentID).collection("yourSubCollection")
yourSubCollectionRef.getDocuments(completion: { (subSnapshot, subErr) in
if let subErr = subErr {
print(subErr.localizedDescription)
return
}
guard let subSnapshot = subSnapshot else { return }
subSnapshot.documents.forEach({ (subDocument) in
let subData = subDocument.data()
let subDocID = UUID().uuidString
let yourNewSubCollectionRef = userRef.collection("Messages").document(messageID).collection("yourSubCollection").document(subDocID)
batch.setData(subData, forDocument: yourNewSubCollectionRef, merge: true)
})
})
})
batch.commit()
}
}