Firebase Document will not delete, probably a timing error, swift - ios

I'm trying to load data into a tableview. Once a user clicks on the cell, it should delete. However, nothing happens. I know that it runs, error is nil, I know that the document ID exists, so I'm unsure as to why the document isn't deleting. Here is some code:
Here's where i load in my data:
self.comments = []
for index in 0...1
{
var postCategory = ""
if index == 0 { postCategory = "cPosts" }
else { postCategory = "rPosts"}
db.collection(postCategory).order(by: "rankValue", descending: true).getDocuments
{ snapshot, error in
guard error == nil else { print(error!); return }
for postDoc in snapshot!.documents
{
self.getUsername(uid: self.uid!)
{ username in
self.secondGroup.enter()
self.ref = self.db.collection(postCategory).document(postDoc.documentID).collection("comments")
self.ref?.whereField("username", isEqualTo: username).getDocuments
{ snapshot, error in
guard error == nil else { print(error!); return }
for doc in snapshot!.documents
{
let comment = Comment(data: doc.data())
comment.docID = doc.documentID
self.comments.append(comment)
self.firstGroup.enter()
self.repRef = self.db.collection(postCategory).document(postDoc.documentID).collection("comments").document(comment.docID).collection("replies")
self.repRef?.whereField("username", isEqualTo: username).getDocuments
{ snapshot, error in
guard error == nil else { print(error!); return }
for doc in snapshot!.documents
{
let reply = Reply(data: doc.data())
reply.docID = doc.documentID
self.comments.append(reply)
}
self.firstGroup.leave()
}
}
self.firstGroup.notify(queue: .main) {
self.secondGroup.leave()
}
self.secondGroup.notify(queue: .main) {
completion(self.ref!, self.repRef!)
}
}
}
}
}
}
Here's where I actually delete the document:
self.ref?.document(self.comments[indexPath.row].docID).collection("replies").getDocuments
{ snapshot, error in
guard error == nil else { print(error!); return }
for doc in snapshot!.documents
{
doc.reference.delete { error in
guard error == nil else { print(error!); return }
}
}
}
self.ref?.document(self.comments[indexPath.row].docID!).delete { error in
guard error == nil else { print(error!); return }
print()
}
self.repRef?.document(self.comments[indexPath.row].docID).delete { error in
print("ranhere also")
guard error == nil else { print(error!); return }
}

Related

Swift Firestore - TableView Button Bug

Please look at my TableView with custom Cell here. I fetched the data from Firestore (and sort it by date descendingly) real time and insert it into the meal array locally. When I clicked the 'eat' button and add a new meal, the 'eat' button isn't sorted correctly?
Here is my code for loadMenu() function and eatButtonPressed() function
loadMenu()
func loadMenu() {
let menuRef = db.collection("menu").document()
db.collection("menu").order(by: "date", descending: true).whereField("family_id", isEqualTo: "\(UserDefaults.standard.string(forKey: "family_id")!)")
.addSnapshotListener { querySnapshot, error in
self.menu = []
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
let name = documents.map { $0["name"] ?? [""] }
let family_id = documents.map { $0["family_id"] ?? [0] }
let portions = documents.map { $0["portions"] ?? [""] }
let menu_id = documents.map { $0["menu_id"] ?? [""]}
let isOpened = documents.map { $0["isOpened"] ?? [""]}
if name != nil || name[0] as! String != "" {
for i in 0..<name.count {
self.menu.append(Menu(menu_id: menu_id[i] as! String, name: name[i] as! String, family_id: family_id[i] as! String, portions: portions[i] as! Int, isOpened: isOpened[i] as! Bool))
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
eatButtonPressed()
func eatButtonPressed(cell: TopPartTableViewCell, send: UIButton) {
var likeRef = self.db.collection("like").document("\(Auth.auth().currentUser!.uid)_\(self.menu[send.tag].menu_id)")
self.db.collection("dislike").document("\(Auth.auth().currentUser!.uid)_\(self.menu[send.tag].menu_id)").delete() { err in
if let err = err {
print("Error removing document: \(err)")
} else {
print("Document successfully removed!")
}
}
likeRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
self.db.collection("like").document("\(Auth.auth().currentUser!.uid)_\(self.menu[send.tag].menu_id)").delete() { err in
if let err = err {
print("Error removing document: \(err)")
} else {
print("Document successfully removed!")
}
}
} else {
likeRef.setData([
"like_id": "\(likeRef.documentID)",
"user_id": "\(Auth.auth().currentUser!.uid)",
"menu_id": "\(self.menu[send.tag].menu_id)"
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
}
}
let user_liked = like.contains(where: {$0.menu_id == menu[send.tag].menu_id}) && like.contains(where: {$0.user_id == Auth.auth().currentUser!.uid})
if !user_liked {
send.backgroundColor = UIColor(named: "BrandOrange")
send.tintColor = UIColor.white
cell.dontEatButton.backgroundColor = UIColor.white
cell.dontEatButton.tintColor = UIColor.black
} else {
send.backgroundColor = UIColor.white
send.tintColor = UIColor.black
}
}
Anyone can help me solve this problem?
Thank you.
The above method doesn't seem to be relevant to the question.
You have added a new item and a cell has been added for that item.
If you look at the detailed structure of the cell, I think you can figure out the cause.
If the cell default button state is Eat, it is most likely not initialized properly.

Firebase / Firestore not getting document and running code

I am getting a document from firebase in swift. However the line isn't being run and is not getting the data.
This is my code:
let db = Firestore.firestore()
db.collection("chats").document(userDefaults.string(forKey: "currentGroup")!).collection("messages").document("variable").getDocument { (document, error) in
if error != nil{
print("error getting document")
}
else{
let documentData = document!.data()
let startNumOfMessages = documentData!["numOfMessages"] as! Int
var messageArray: Array<String> = []
if startNumOfMessages > 0 {
for message in 1...startNumOfMessages{
print(message)
//THIS LINE ISNT RUNNING
db.collection("chats").document(self.userDefaults.string(forKey: "currentGroup")!).collection("messages").document("\(message)").getDocument { (messageDoc, err) in
if err != nil{
print("Error getting message \(message)")
}
else{
if messageDoc!.exists && messageDoc != nil{
let messData = messageDoc!.data()
print(messData!["message"]!)
messageArray.append(messData!["message"] as! String)
}
else{
print("error in document")
}
}
}
}
//Display them
for num in 0...messageArray.count{
let label = UILabel()
label.text = messageArray[num]
self.stackView.addArrangedSubview(label)
}
}
}
}
The line below the comment is the line that isn't running. And the line that says label.text = messageArray[num] displays an error
Fatal error: Index out of range
Showing it doesn't get the data.
You miss the asynchronous way use DispatchGroup ( numbered from 1 to 4 )
let db = Firestore.firestore()
db.collection("chats").document(userDefaults.string(forKey: "currentGroup")!).collection("messages").document("variable").getDocument { (document, error) in
if error != nil{
print("error getting document")
}
else{
let documentData = document!.data()
let startNumOfMessages = documentData!["numOfMessages"] as! Int
var messageArray: Array<String> = []
if startNumOfMessages > 0 {
let g = DispatchGroup() /// 1
for message in 1...startNumOfMessages{
print(message)
//THIS LINE ISNT RUNNING
g.enter() /// 2
db.collection("chats").document(self.userDefaults.string(forKey: "currentGroup")!).collection("messages").document("\(message)").getDocument { (messageDoc, err) in
if err != nil{
print("Error getting message \(message)")
}
else{
if messageDoc!.exists && messageDoc != nil{
let messData = messageDoc!.data()
print(messData!["message"]!)
messageArray.append(messData!["message"] as! String)
}
else{
print("error in document")
}
}
g.leave() /// 3
}
}
g.notify(queue: .main) { /// 4
//Display them
for num in 0...messageArray.count{
let label = UILabel()
label.text = messageArray[num]
self.stackView.addArrangedSubview(label)
}
}
}
}
}

How can I delete a Firestore field, using whereField?

I'm trying to delete a field within a document, when the field "uid" matches the Current User's ID. I'm pretty stuck on this, and would appreciate any help. I detail, below, my code and how my database is set up.
#IBAction func deleteAccountButtonIsTapped(_ sender: Any) {
let db = Firestore.firestore()
let userID = Auth.auth().currentUser?.uid
let username = usernameTextField.placeholder
Auth.auth().currentUser?.delete(completion: { (error) in
if error != nil {
print("ERROR MAIN SETTINGS 136")
} else {
db.collection("FollowerList").whereField("uid", isEqualTo: userID!).getDocuments { (snapshot, error) in
for snapshot in snapshot?.documents {
}
}
}
}
)}
My Database has a collection "FollowerList", with documents named with the User's UID. Within these documents is a "uid" field, with the value of the User's UID.
Any help would be massively appreciated.
This should do the Job:
func deleteAccountButtonIsTapped(_ sender: Any) {
let db = Firestore.firestore()
let userID = Auth.auth().currentUser?.uid
let username = usernameTextField.placeholder
Auth.auth().currentUser?.delete(completion: { (error) in
if error != nil {
print("ERROR MAIN SETTINGS 136")
} else {
db.collection("FollowerList").whereField("uid", isEqualTo: userID!).getDocuments { (snapshot, error) in
if let snapshot = snapshot?.documents {
for doc in snapshot {
//Do delete
db.collection("FollowerList").document(doc.documentID).updateData([
"fieldToDelete": FieldValue.delete(),
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
}
}
}
}
}
}
)}
One would think it could work like this:
But it doesn't as a value of type 'QueryDocumentSnapshot' has no member 'updateData'.
func deleteAccountButtonIsTapped(_ sender: Any) {
let db = Firestore.firestore()
let userID = Auth.auth().currentUser?.uid
let username = usernameTextField.placeholder
Auth.auth().currentUser?.delete(completion: { (error) in
if error != nil {
print("ERROR MAIN SETTINGS 136")
} else {
db.collection("FollowerList").whereField("uid", isEqualTo: userID!).getDocuments { (snapshot, error) in
if let snapshot = snapshot?.documents {
for doc in snapshot {
// How one would think it works but it doesnt
doc.updateData([
"capital": FieldValue.delete(),
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
}
}
}
}
}
}
)}
See this page for further information:
https://firebase.google.com/docs/firestore/manage-data/delete-data#swift

Optional Still Returning Nil After Assigning Value

I am working on a similar feature to 'liking/unliking a post'.
I have an MVVM architecture as;
struct MyStructModel {
var isLiked: Bool? = false
}
class MyStructView {
var isLiked: Bool
init(myStructModel: MyStructModel) {
self.isLiked = myStructModel.isLiked ?? false
}
}
I successfully get the value of whether the post is liked or not here;
func isPostLiked(documentID: String, completion: #escaping (Bool) -> Void) {
guard let authID = auth.id else { return }
let query = reference(to: .users).document(authID).collection("liked").document(documentID)
query.getDocument { (snapshot, error) in
if error != nil {
print(error as Any)
return
}
guard let data = snapshot?.data() else { return }
if let value = data["isLiked"] as? Bool {
completion(value)
} else {
completion(false)
}
}
}
func retrieveReviews(completion: #escaping([MyStructModel]) -> ()) {
var posts = [MyStructModel]()
let query = reference(to: .posts).order(by: "createdAt", descending: true)
query.getDocuments { (snapshot, error) in
if error != nil {
print(error as Any)
return
}
guard let snapshotDocuments = snapshot?.documents else { return }
for document in snapshotDocuments {
if var post = try? JSONDecoder().decodeQuery(MyStructModel.self, fromJSONObject: document.decode()) {
// isLiked is nil here...
self.isPostLiked(documentID: post.documentID!) { (isLiked) in
post.isLiked = isLiked
print("MODEL SAYS: \(post.isLiked!)")
// isLiked is correct value here...
}
posts.append(post)
}
completion(posts)
}
}
}
However, when it gets to my cell the value is still nil.
Adding Cell Code:
var post: MyStructView? {
didSet {
guard let post = post else { return }
print(post.isLiked!)
}
}
Your isLiked property is likely nil in your cells because the retrieveReviews function doesn't wait for the isPostLiked function to complete before completing itself.
You could easily solve this issue by using DispatchGroups. This would allow you to make sure all of your Posts have their isLiked value properly set before being inserted in the array, and then simply use the DispatchGroup's notify block to return all the loaded posts via the completion handler:
func retrieveReviews(completion: #escaping([MyStructModel]) -> ()) {
var posts = [MyStructModel]()
let query = reference(to: .posts).order(by: "createdAt", descending: true)
query.getDocuments { [weak self] (snapshot, error) in
guard let self = self else { return }
if error != nil {
return
}
guard let documents = snapshot?.documents else { return }
let dispatchGroup = DispatchGroup()
for document in documents {
dispatchGroup.enter()
if var post = try? JSONDecoder().decodeQuery(MyStructModel.self, fromJSONObject: document.decode()) {
self.isPostLiked(documentID: post.documentID!) { isLiked in
post.isLiked = isLiked
posts.append(post)
dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .main) {
completion(posts)
}
}
}

CKQueryOperation queryCompletionBlock return a nil cursor

I'm fetching a CloudKit database with CKQueryOperation. For some reason every time when I press a fetch button, the first time I get a nil cursor. The second time it fetches and gets data, it's all good. When I check the recordFetchedBlock it does get the results and appends them, but at the end the array is empty. I don't understand why this happens. I want to show the results immediately since they have been fetched. I think the problem is with the nil cursor, but I'm open for other suggestions. Here's my code:
public class CloudKitDatabase {
static let shared = CloudKitDatabase()
var records = [CKRecord]()
let publicData = CKContainer.default().publicCloudDatabase
init() {
self.fetchRecords()
}
func fetchRecords() {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "OECD", predicate: predicate)
let queryOperation = CKQueryOperation(query: query)
queryOperation.recordFetchedBlock = {
record in
self.records.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
DispatchQueue.main.async {
if error != nil {
print(error.debugDescription)
} else {
if cursor != nil {
self.queryServer(cursor!)
} else {
print("CURSOR IS NIL")
}
}
}
}
self.publicData.add(queryOperation)
}
func queryServer(_ cursor: CKQueryOperation.Cursor) {
let queryOperation = CKQueryOperation(cursor: cursor)
queryOperation.recordFetchedBlock = {
record in
self.records.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
DispatchQueue.main.async {
if error != nil {
print(error.debugDescription)
} else {
if cursor != nil {
self.queryServer(cursor!)
} else {
print("CURSOR IS NIL")
}
}
}
}
self.publicData.add(queryOperation)
}
The Debug area tells me that:
CURSOR IS NIL
and CloudKitDatabase.shared.records.isEmpty is true
First try some configs on the first query;
let queryOperation = CKQueryOperation(query: query)
queryOperation.queuePriority = .veryHigh
queryOperation.resultsLimit = 99 // built in limit is 400
Next, don't do the cursor calls in a dispatch and include your completions;
queryOperation.queryCompletionBlock =
{ cursor, error in
if error != nil {
print(error.debugDescription)
} else {
if cursor != nil {
self.queryServer(cursor!)
} else {
print("CURSOR IS NIL")
completion(nil)
}
}
}
and;
queryOperation.queryCompletionBlock =
{ cursor, error in
if error != nil {
print(error.debugDescription)
} else {
if cursor != nil {
self.queryServer(cursor!)
} else {
print("CURSOR IS NIL")
completion(nil)
}
}
}
also don't forget to empty your records array at the beginning of fetchRecords otherwise successive calls will get the same records in the array.

Resources