Display firebase document field to iOS tableview - swift

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)
})
})

Related

How do I delete a cell from tableview

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

How do I access Firestore field value without knowing the name?

I am uploading data to Firestore like so:
func uploadTrackedSymptomValues(symptom: String, comment: String, time: String, timestamp: Int) {
print("Uploading symptom values.")
let user_id = FirebaseManager.shared.user_id
let docRef = db.collection("users").document(user_id!).collection("symptom_data").document("\(symptom)_data")
let updateData = [String(timestamp) : ["symptom" : symptom, "severity" : severity, "comment" : comment, "timestamp" : String(timestamp)]]
docRef.setData(updateData, merge: true)
docRef.setData(updateData, merge: true) { (err) in
if err != nil {
print(err?.localizedDescription as Any)
self.view.makeToast(err?.localizedDescription as! String)
} else {
print("Symptom Data Uploaded")
self.view.makeToast("\(symptom) logged at \(time). Severity: \(self.severity). Comment: \(comment)", duration: 2.0, position: .center, title: "Success!", image: self.cellImage) { didTap in
if didTap {
print("completion from tap")
} else {
print("completion without tap")
}
}
}
}
}
On Firestore this looks like:
Now I want to use each field under the timestamp, for a UITableViewCell But I am not sure how to access the field under the document, for example 1556998898 under Anxiety_data so I can access:
comment = "comment";
severity = "Mild";
symptom = "Anxiety";
timestamp = 1556998898;
To use in the UITableViewCell, With realtime database I would use childAdded listener. I have tried:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let index = indexPath.row
let cell = tableView.dequeueReusableCell(withIdentifier: "EntryDataCell", for: indexPath) as! EntryDataCell
cell.configureCustomCell()
let user_id = FirebaseManager.shared.user_id
let symptomRef = db.collection("users").document(user_id!).collection("symptom_data").document(symptomSections[index])
symptomRef.getDocument(completion: { (document, err) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
print("\(document.documentID) : \(dataDescription)")
cell.commentLabel.text = dataDescription //<- Stuck here
} else {
print("Document does not exist")
}
})
return cell
}
But I am stuck as to how I would get the specific field, as I don't know the name as it is the timestamp of when it was made, rather than the whole document. If there is a better way to handle this please let me know. Thank you.
EDIT:
I am able to get the Key (Timestamp) and Values but am having trouble parsing the values:
let obj = dataDescription
for (key, value) in obj! {
print("Property: \"\(key as String)\"") //<- prints Timestamp
print("Value: \"\(value)\"") // <- prints the comment,severity,symptom,timestamp fields I need.
}
If I try to do cell.titleLabel.text = value["comment"] I get Value of type 'Any' has no subscripts
I thought about using a struct:
struct FieldValues: Codable {
let comment: String
let severity: String
let symptom: String
let timestamp: Int
}
But am unsure on how to use it with the value. I feel I am in a bit over my head for this last part.

load large data from firestore to table view Swift

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

Firestore - Creating a copy of a collection

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()
}
}

Passing a document reference to a second view controller

Can someone please show me how I can make it so that when I click on a specific row in a tableView, a document reference is passed to a second ViewController allowing me to show the fields inside the subcollection "Friends". At the moment I can do this but without using autoID. Please may someone how I can do this using an autoID? Any help would be greatly appreciated, many thanks!!
Current Firebase Console
What I would like - Firebase Console
First ViewController
func loadData() {
db.collection("Users").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 newName = UsersNames(name: name)
self.nameArray.append(newName)
}
self.tableView.reloadData()
}
}
}
Second ViewController
func loadData() {
db.collection("Users").document("Hello").collection("Friends").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 newFriends = Friends(friendName: name, friendDetails: details)
self.friendsArray.append(newFriends)
}
self.tableView.reloadData()
}
}
}
If you want to do it that way, firstly you need to add a documentID property to your UsersNames object:
struct UsersNames {
var documentID: String //<-- Add this
var name: String
}
Then update your loadData() function in your First VC to get the documentID from each Firestore document and append in into your Array:
for document in querySnapshot!.documents {
let data = document.data()
let documentID = document.documentID //<-- Add this
let name = data["name"] as? String ?? ""
let newName = UsersNames(documentID: documentID, name: name) //<-- Change this
self.nameArray.append(newName)
}
In First VC, you want to perform a Segue to your Second VC when a cell is selected, and pass the documentID for the selected object in your Array to the Second VC.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "SecondVC", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let indexPath = tableView.indexPathForSelectedRow {
let destinationVC = segue.destination as! SecondVC
let documentId = nameArray[indexPath.row].documentID
destinationVC.documentID = documentID
}
}
In your SecondVC create a property to receive the documentID:
var documentID: String!
In your SecondVC loadData() function, you can now access the documentID that was passed from your First VC:
db.collection("Users").document(documentID).collection("Friends").getDocuments() { //..