How to stop observe - swift

I have this code for retrieving data from Firebase database. The code works fine, but it retrieves the same data multiple times.
How do I stop observe so that the data will only be called once
My code
self.eventGalleryImage.child("event")
.child(self.eventName.text!)
.child("EventImages")
.observeSingleEvent(of: .value, with: { (snapshot) in
if let snapshots = snapshot.children.allObjects as? [FIRDataSnapshot] {
for child in snapshots {
if let dict = child.value as? Dictionary<String, Any> {
if let userGallery = dict["Text"] as? String {
self.postsFindGallery.insert(galleryStruct(gallery: userGallery), at: 0)
self.collectionView.reloadData()
}
}
}
}
})

A lesson to be learnt. Do all you can to avoid using for loops when observing data. This is what you'll need to do to get all of the data inside that database path ONCE:
self.eventGalleryImage.child("event")
.child(self.eventName.text!)
.child("EventImages")
.observe(.childAdded, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: AnyObject] else { return }
if let userGallery = dictionary["Text"] as? String {
self.postsFindGallery.insert(galleryStruct(gallery: userGallery), at: 0)
self.collectionView.reloadData()
}
}, withCancel: nil)
You may also want to try:
DispatchQueue.main.async {
self.collectionView.reloadData()
}

Related

Pagination not working in Swift with Firebase

I am trying to paginate my data pull. But somehow it is not working and I am not sure what I am doing wrong.
In my viewDidLoad I call this function:
var currentKey: String?
func fetchNotifications() {
guard let currentUid = Auth.auth().currentUser?.uid else { return }
if currentKey == nil {
//initial data pull
NOTIFICATIONS_REF.child(currentUid).queryLimited(toLast: 16).observeSingleEvent(of: .value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard let allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
let notificationId = snapshot.key
guard let dictionary = snapshot.value as? Dictionary<String, AnyObject> else { return }
guard let uid = dictionary["uid"] as? String else { return }
Database.fetchUser(with: uid, completion: { (user) in
// if notification is for post
if let postId = dictionary["postId"] as? String {
Database.fetchPost(with: postId, completion: { (post) in
let notification = Notification(user: user, post: post, dictionary: dictionary)
if notification.notificationType == .Comment {
self.getCommentData(forNotification: notification)
}
self.notifications.append(notification)
self.notifications.sort { (notification1, notification2) in
return notification1.creationDate > notification2.creationDate
}
self.tableView.reloadData()
})
} else {
let notification = Notification(user: user, dictionary: dictionary)
self.notifications.append(notification)
self.notifications.sort { (notification1, notification2) in
return notification1.creationDate > notification2.creationDate
}
self.tableView.reloadData()
self.tableView.refreshControl?.endRefreshing()
}
})
})
self.tableView.refreshControl?.endRefreshing()
}
} else {
NOTIFICATIONS_REF.child(currentUid).queryOrderedByKey().queryEnding(atValue: self.currentKey).queryLimited(toLast: 3).observeSingleEvent(of: .value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard let allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
let notificationId = snapshot.key
guard let dictionary = snapshot.value as? Dictionary<String, AnyObject> else { return }
guard let uid = dictionary["uid"] as? String else { return }
Database.fetchUser(with: uid, completion: { (user) in
if notificationId != self.currentKey {
// if notification is for post
if let postId = dictionary["postId"] as? String {
Database.fetchPost(with: postId, completion: { (post) in
let notification = Notification(user: user, post: post, dictionary: dictionary)
if notification.notificationType == .Comment {
self.getCommentData(forNotification: notification)
}
self.notifications.append(notification)
self.notifications.sort { (notification1, notification2) in
return notification1.creationDate > notification2.creationDate
}
self.tableView.reloadData()
})
} else {
let notification = Notification(user: user, dictionary: dictionary)
self.notifications.append(notification)
self.notifications.sort { (notification1, notification2) in
return notification1.creationDate > notification2.creationDate
}
self.tableView.reloadData()
self.tableView.refreshControl?.endRefreshing()
}
}
})
})
}
}
}
I am populating the currentKey so it is no longer nil. The idea is that the else-statement then comes into place. But the else-statement is not being called .. I was also printing the currentKey after the initial data pull, so I know it is being populated, but still the else does not come into action.

Swift - Remove key and values from dictionary [String : Any]

I am trying to removed block users from a dictionary [String : Any] that I am grabbing from the database. At first I grab the list of UID's that the current user has blocked:
var updatedBlockList: Any?
func findBlockUsers() {
// find current users list of blocked users
guard let currentUserUid = Auth.auth().currentUser?.uid else { return }
let blockedUsers = Database.database().reference().child("users").child(currentUserUid)
blockedUsers.observeSingleEvent(of: .value, with: { (snapshot) in
guard let userIdsDictionary = snapshot.value as? [String: Any] else { return }
userIdsDictionary.forEach({ (key, value) in
guard let userDictionary = value as? [String: Any] else { return }
var blockedList : Any
blockedList = userDictionary.keys
print(blockedList)
self.updateBlockList(blockedList: blockedList)
})
})
}
func updateBlockList(blockedList: Any) {
updatedBlockList = blockedList
print(updatedBlockList)
}
If I print updatedBlockList I get: ["gdqzOXPWaiaTn93YMJBEv51UUUn1", "RPwVj59w8pRFLf55VZ6LGX6G2ek2", "xmigo8CPzhNLlXN4oTHMpGo7f213"]
I now want to take those UID's (which will be the key in UserIdsDictionary and remove them after I pull ALL the users:
fileprivate func fetchAllUserIds() {
let ref = Database.database().reference().child("users")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let userIdsDictionary = snapshot.value as? [String: Any] else { return }
userIdsDictionary.forEach({ (key, value) in
// attempting to remove the blocked users here without any luck
var updatedKey = key as String?
updatedKey?.remove(at: self.updatedBlockList as! String.Index)
print(updatedKey!)
guard let userDictionary = value as? [String: Any] else { return }
let user = User(uid: key, dictionary: userDictionary)
self.fetchPostsWithUser(user: user)
})
}) { (err) in
print("Failed to fetch following user ids:", err)
}
}
I get this error when trying to remove: Could not cast value of type 'Swift.Dictionary.Keys' (0x1de2f6b78) to 'Swift.String.Index'
I'm sure i'm going about this the wrong way, but I know i'm close. The end goal is to take the blocked users UID's and remove them from the dictionary. Any help would be very much appreciated!!
Your forEach loop on userIdsDictionary is the wrong approach here so rather than trying to fix that code I would use a different approach and loop over the updatedBlockList
for item in updatedBlockList {
if let userID = item as? String {
userIdsDictionary.removeValue(forKey: userID)
}
}
For anyone wondering, here is the final changes that were made to make it work.
var updatedBlockList = [String]()
func findBlockUsers() {
// find current users list of blocked users
guard let currentUserUid = Auth.auth().currentUser?.uid else { return }
let blockedUsers = Database.database().reference().child("users").child(currentUserUid)
blockedUsers.observeSingleEvent(of: .value, with: { (snapshot) in
guard let userIdsDictionary = snapshot.value as? [String: Any] else { return }
userIdsDictionary.forEach({ (key, value) in
guard let userDictionary = value as? [String: Any] else { return }
let blockedList = Array(userDictionary.keys)
print(blockedList)
self.updateBlockList(blockedList: blockedList)
})
})
}
func updateBlockList(blockedList: [String]) {
updatedBlockList = blockedList
print(updatedBlockList)
}
fileprivate func fetchAllUserIds() {
let ref = Database.database().reference().child("users")
ref.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in
guard var userIdsDictionary = snapshot.value as? [String: Any], let self = self else { return }
for item in self.updatedBlockList {
userIdsDictionary.removeValue(forKey: item)
}
userIdsDictionary.forEach({ (key, value) in
guard let userDictionary = value as? [String: Any] else { return }
let user = User(uid: key, dictionary: userDictionary)
self.fetchPostsWithUser(user: user)
})
}) { (err) in
print("Failed to fetch following user ids:", err)
}
}

How to know when Firebase has finished downloading all the nodes in a snapshot using Swift?

I have the following function which fetches messages from Firebase, I want to know when the Firebase has finished loading all child nodes of the data snapshot so I can pass back a signal via handler that all the data has finished loading.
func loadLatestChatConversationMessages(forUserId forId: String!, handler: #escaping (Message?) -> ()){
guard let uid = Auth.auth().currentUser?.uid else {return}
let ref = REF_USERS_MESSAGES.child(uid).child(forId)
let firstBatchCount: Int = 12
RefUserMessagesChatConvoHandle = ref.queryOrderedByKey().queryLimited(toLast: UInt(firstBatchCount)).observe(.value, with: { (snapshot) in
if snapshot.childrenCount > 0 {
for child in snapshot.children.allObjects as! [DataSnapshot]{
let key = child.key
self.REF_MESSAGES.child(key).observeSingleEvent(of: .value, with: { (snapshot) in
guard var dictionary = snapshot.value as? [String : Any] else { return }
dictionary["messageId"] = key
let message = Message(dictionary: dictionary)
handler(message)
}, withCancel: nil)
}//end for
} else {
print("returning - no messages.")
handler(nil)
}// end if snapshot.childrenCount
}, withCancel: nil)
}//end func
on the top create a dispatchGroup object and enter() right away.
https://developer.apple.com/documentation/dispatch/dispatchgroup
let dg = DispatchGroup()
and then in each child download you want to do you enter() and leave() once its done.
then finally you call
dg.notify(queue: .main, work: {
// escape the close
})

Swift UIActivityViewController only showing one item when exporting as text

I'm trying to allow the user to export a list of items to notes but for some reason, it's only exporting the first item and not the entire list like I would hope it was going to. This is my attempt at doing this...
guard let listId = list?.id else { return }
let ref = Database.database().reference().child("lists").child(listId).child("list-items")
ref.observe(.value) { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
dict.forEach({ (key, value) in
let itemRef = Database.database().reference().child("items").child(key)
itemRef.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
guard let itemName = dictionary["item"] as? String else { return }
let activityController = UIActivityViewController(activityItems: [itemName], applicationActivities: nil)
self.present(activityController, animated: true, completion: nil)
})
})
}

Firebase - Swift - Delete a child UID from a snapshot

I am trying to delete up the studentUser UID from my staffUsers. The UID I want to delete is nested in the staffUsers -> studentSession1List.
I have the UID listed with A Bool of True on creation. This "studentSession1List" will have lots of studentUsers in the list. I only want the studentUser that is logged in to have their UID(55FDLm9n6LccZBB7skaCbvfSHRz1) removed from the list.
let dataref = Database.database().reference()
dataref.child("staffUsers").queryOrdered(byChild: "studentSession1List").observe(.value, with: { (snapshot) in
for snap in snapshot.children {
guard let studentUID = Auth.auth().currentUser?.uid else { return }
let snapDataSnapshot = snap as! DataSnapshot
var snapValues = snapDataSnapshot.value as? [String: AnyObject]
if var snapWithReg = snapValues?["studentSession1List"] as? [String: Bool] {
print("This is the staff member")
print(snapWithReg)
print(snapWithReg.count)
snapWithReg.removeValue(forKey: studentUID)
}
}
}) { (error) in
print(error.localizedDescription)
}
Here is the output:
Full Function for Deleting and Adding the Student
func didSelect(for cell: StudentSearchCell) {
guard let indexpath = collectionView?.indexPath(for: cell) else { return }
let staffUser = self.users[indexpath.item]
let selectedUserId = staffUser.uid
guard let studentUID = Auth.auth().currentUser?.uid else { return }
let dataRef = Database.database().reference()
dataRef.child("staffUsers").queryOrdered(byChild: "studentSession1List").observe(.value, with: { (snapshot) in
for snap in snapshot.children {
guard let studentUID = Auth.auth().currentUser?.uid else { return }
let snapDataSnapshot = snap as! DataSnapshot
var snapValues = snapDataSnapshot.value as? [String: AnyObject]
if (snapValues? ["studentSession1List"] as? [String: Bool]) != nil {
dataRef.child("staffUsers").child(snapDataSnapshot.key).child("studentSession1List").child(studentUID).removeValue(completionBlock: { (error, ref) in
if error != nil {
print("Error: \(String(describing: error))")
return
}
print("Removed successfully")
})
}
}
}) { (error) in
print(error.localizedDescription)
}
// Add student to staff list
let ref = Database.database().reference().child("staffUsers").child(selectedUserId).child("studentSession1List")
let values = [studentUID: true]
ref.updateChildValues(values) { (err, ref) in
if let err = err {
print("Failed to follow user:", err)
return
}
}
// Add selected staff to student list
let studentRef = Database.database().reference().child("studentUsers").child(studentUID).child("studentSession1List")
studentRef.removeValue()
let studentValues = [selectedUserId: true]
studentRef.updateChildValues(studentValues) { (err, studentRef) in
if let err = err {
print("Failed to follow user:", err)
return
}
}
self.navigationController?.popViewController(animated: true)
}
I think you need to reach the child that you want to remove using the following code and then remove it.
Edit1:
Since inside staffUsers we have keys inside which studentSession1List is present inside which the value (studentUID) is present that we want to remove, so inside your already written code I have added the new code, please check
let dataref = Database.database().reference()
dataref.child("staffUsers").queryOrdered(byChild: "studentSession1List").observe(.value, with: { (snapshot) in
for snap in snapshot.children {
guard let studentUID = Auth.auth().currentUser?.uid else { return }
let snapDataSnapshot = snap as! DataSnapshot
var snapValues = snapDataSnapshot.value as? [String: AnyObject]
if var snapWithReg = snapValues?["studentSession1List"] as? [String: Bool] {
//Added code here
dataref.child("staffUsers").child(snapDataSnapshot.key).child("studentSession1List").child(studentUID).removeValue(completionBlock: { (error, ref) in
if error != nil {
print("Error: \(error)")
return
}
print("Removed successfully")
})
}
}
}) { (error) in
print(error.localizedDescription)
}
Edit2:
To delete the code once , we can use observeSingleEvent
observeSingleEvent(of: .value, with: { (snapshot) in
}, withCancel: nil)