I fetch data and display in a tableView, the problem is the data is not executing in the correct order.
I have tried:
for case let child as DataSnapshot in data!.children.reversed() {
let newDispatchGroup = DispatchGroup()
let commentID = child.key
let uid = child.childSnapshot(forPath: "UID").value as! String
let commentText = child.childSnapshot(forPath: "Comment").value!
let timeStamp = child.childSnapshot(forPath: "timeStamp").value!
let date = ConvertDate(mediaTimestamp: timeStamp as! Double).getDate!
//print(date, "dsfsdafdasfdsafdsahjkfhfdsafsajkadhffdsfsafsasjkfhsdajkhfdsajkhfjklads")
newDispatchGroup.enter()
ref.child("users2").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot, "dshjkfhjkadhfsjkfhsdajkhfdsajkhfjklads")
print(date, "dsfsdafdasfdsafdsahjkfhfdsafsajkadhffdsfsafsasjkfhsdajkhfdsajkhfjklads")
let username = snapshot.childSnapshot(forPath: "username").value
let profileImage = snapshot.childSnapshot(forPath: "profileImage").value
let newUser = User(theuserID: uid, theUsername: username as! String, theprofImage: profileImage as! String)
let newComment = Comment(newUser: newUser, text: commentText as! String, timeStamp: date, NcommentID: commentID)
self.commentsVC1.arrayOfComments.append(newComment)
newDispatchGroup.leave()
//completion()
})
newDispatchGroup.notify(queue: .main, execute: {
print(self.totalComments, "COgfdsdfgfdsgdsfgdfsgfdsgdfsgdskj", self.commentsVC1.arrayOfComments.count)
if self.totalComments == self.commentsVC1.arrayOfComments.count {
print("COmejkfbdshkafdsagfhksdagfdsakj")
self.commentsVC1.tableView.reloadData()
}
})
}
})
}
But it did not work either, the order in which the second firebase calls execute is incorrect.
You should set up your notify closure when you set up your DispatchGroup. And you would not need to use a completion closure for you loadComments function.
let dispatchGroup = DispatchGroup()
dispatchGroup.notify(queue: .main, execute: {
if self.totalComments == self.commentsVC1.arrayOfComments.count {
print("COmejkfbdshkafdsagfhksdagfdsakj")
self.commentsVC1.tableView.reloadData()
}
})
loadComments()
notify will be called, when leave has been called the same amount of times as enter. In your code the last leave call is happening before you have set the anything to be notified about.
I solved with this:
for case let child as DataSnapshot in snap.children.reversed() {
let commentID = child.key
let uid = child.childSnapshot(forPath: "UID").value as! String
let commentText = child.childSnapshot(forPath: "Comment").value!
let timeStamp = child.childSnapshot(forPath: "timeStamp").value!
let date = ConvertDate(mediaTimestamp: timeStamp as! Double).getDate!
let newUser = User(theuserID: uid)
let newComment = Comment(newUser: newUser, text: commentText as! String, timeStamp: date, NcommentID: commentID)
self.commentsVC1.arrayOfComments.append(newComment)
ref.child("users2").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
let username = snapshot.childSnapshot(forPath: "username").value
let profileImage = snapshot.childSnapshot(forPath: "profileImage").value
let newUserIner = User(theuserID: uid, theUsername: username as! String, theprofImage: profileImage as! String)
newComment.user = newUserIner
if self.totalComments == self.commentsVC1.arrayOfComments.count {
self.commentsVC1.tableView.reloadData()
}
})
}
I would use dispatch group here though so one does not have have to check if its done unnecessarily.
Related
Hello i am creating Chat App Using Swift And Firebase i am fetching messages and populating in tableview but when i send new message then tableview dislplaying multiple entry i am useing below code for fetching messages
func fetchAllMessage(){
guard let uid = Auth.auth().currentUser?.uid else { return }
let fetchMsgGroup = Database.database().reference().child("user-messages_group_iOS").child(uid).child(self.chatID)
fetchMsgGroup.observe(.value, with: { (snapshot) in
if snapshot.exists(){
if let dictonary = snapshot.value as? [String:AnyObject]{
self.groupMessageData.removeAll()
if let userMessages = dictonary["userMessages"] as? [String:AnyObject]{
for (key, _) in userMessages{
let messagesFrtchRef = Database.database().reference().child("messages_iOS").child(key)
messagesFrtchRef.observe(.value, with: { (snapshot1) in
if snapshot1.exists(){
if let dict = snapshot1.value as? [String:AnyObject]{
let fromId = dict["fromId"] as! String
let messageUID = dict["messageUID"] as! String
let seen = dict["seen"] as! Bool
let status = dict["status"] as! String
let text = dict["text"] as! String
let timestamp = dict["timestamp"] as! Double
let told = dict["told"] as! String
let messages = GroupMessage(fromId: fromId, messageUID: messageUID, seen: seen, status: status, text: text, timestamp: timestamp, told: told)
self.groupMessageData.insert(messages, at: 0)
}
self.tblListView.reloadData()
}else{
}
}, withCancel: nil)
}
}
}
}else{
}
}, withCancel: nil)
}
i have tried everything like clearing removing also clearing observer when needed but its not work enough for me is their anyone have any solution for this then please help me
can anyone help me to solve this out
I try to retrieve data from Firebase into Array. Because it runs asynchronously, the results that I want to show in my CollectionView is a delay until I switch back and forth. I am very new to asynchronous functions in iOS. Please help me to complete my code.
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
}
})
You need to reload the collection after you retrieve the data so after the for loop call reloadData()
for child in result {
}
self.collectionView.reloadData()
//
func getValueFromDatabase(completion: #escaping (_ status: Bool) -> Void){
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
completion(true)
}
else {
completion(false)
}
})
}
//
self.getValueFromDatabase { (status) in
if status {
// success
}
}
I'm working with Firebase in my project right now. I would suggest the following solution: wrap the database observer in a distinct function which gets completion block as a parameter.
func getValueFromDatabase(completion: ()->Void){
ref = Database.database().reference(withPath: "MyTest/Video")
ref?.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
let autoID = child.key as String //get autoID
let title = snapshot.childSnapshot(forPath: "\(autoID)/Title").value
let url = snapshot.childSnapshot(forPath: "\(autoID)/URL").value
let views = snapshot.childSnapshot(forPath: "\(autoID)/Views").value
self.arrayAllTitle.append(title as! String)
self.arrayAllId.append(url as! String)
self.arrayAllDesc.append(views as! String)
}
completion()
}
})
}
This way you can call the function from anywhere providing the desired action after fetching data from db is finished:
getValueFromDatabase(completion:{
self.collectionView.reloadData() //or any other action you want to fulfil
})
I am implementing a social media application using Swift. In the MyProfileViewController, I have used UIImagePickerController to change the current user's profile picture. However, usage of UIImagePickerController causes a duplicate of table view rows even though I handle the table view in viewDidLoad, not in viewWillAppear.
To illustrate, here is my viewDidLoad function.
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
self.automaticallyAdjustsScrollViewInsets = false
ref = Database.database().reference()
storageRef = Storage.storage().reference()
ref.child("users").child((Auth.auth().currentUser?.uid)!).observe(.value, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
let userId = value?["id"] as! String
let username = value?["username"] as! String
let email = value?["userEmail"] as! String
let profilePictureUrl = value?["profilePicture"] as! String
self.currentUser = User(id: userId, username: username, email: email, profilePicture: profilePictureUrl)
self.tableView.reloadData()
})
ref.child("posts").observe(.childAdded, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
let id = value?["id"] as! String
let tags = value?["tags"] as! String
let facultyName = value?["faculty"] as! String
let courseName = value?["course"] as! String
let questionTitle = value?["title"] as! String
let questionText = value?["description"] as! String
let dateAndTime = value?["dateAndTime"] as! String
let userID = value?["user-id"] as! String
self.ref.child("users").child(userID).observe(.value, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
let userId = value?["id"] as! String
let username = value?["username"] as! String
let email = value?["userEmail"] as! String
let profilePictureUrl = value?["profilePicture"] as! String
let post = Post(id: id, tags: tags, facultyName: facultyName, courseName: courseName, questionTitle: questionTitle, questionText: questionText, dateAndTime : dateAndTime, username: username)
if userID == Auth.auth().currentUser?.uid {
self.posts.insert(post, at: 0)
}
self.postDictionary[id] = post
let member = User(id: userId, username: username, email: email, profilePicture: profilePictureUrl)
if post.username == member.username {
self.userDictionary[id] = member
}
self.tableView.reloadData()
})
})
}
And here is my UIImagePickerController function:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let indexPath = NSIndexPath(row: 0, section: 0)
let cell = tableView.cellForRow(at: indexPath as IndexPath) as! MyProfileInfoCell!
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
selectedImage = image
cell?.profilePicture.contentMode = .scaleAspectFit
cell?.profilePicture.image = image
}
var data = NSData()
data = UIImageJPEGRepresentation((cell?.profilePicture.image!)!, 0.8)! as NSData
let filePath = "\((Auth.auth().currentUser?.uid)!)" // path where you wanted to store img in storage
let metaData = StorageMetadata()
metaData.contentType = "image/jpg"
self.storageRef = Storage.storage().reference()
self.storageRef.child(filePath).putData(data as Data, metadata: metaData) {
(metaData,error) in
if let error = error {
print(error.localizedDescription)
return
} else{
let downloadURL = metaData!.downloadURL()!.absoluteString
self.ref.child("users").child((Auth.auth().currentUser?.uid)!).updateChildValues(["profilePicture" : downloadURL])
}
}
self.dismiss(animated: true, completion: nil)
print(self.posts.count)
}
My table view consists of 2 prototype cells. The first prototype cell is for the profile information, in which changing profile picture is handled. The second prototype cell is for showing the posts posted by that user. The problem occurs here. When I change my profile picture, the posts rows are being duplicated.
Can anyone help me with this?
The observe method adds an observer to "users" on your DB. Every time you changes the "users" on DB the following code is executed and a post is added to tableview:
self.ref.child("users").child(userID).observe(.value, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
let userId = value?["id"] as! String
let username = value?["username"] as! String
let email = value?["userEmail"] as! String
let profilePictureUrl = value?["profilePicture"] as! String
let post = Post(id: id, tags: tags, facultyName: facultyName, courseName: courseName, questionTitle: questionTitle, questionText: questionText, dateAndTime : dateAndTime, username: username)
if userID == Auth.auth().currentUser?.uid {
self.posts.insert(post, at: 0)
}
self.postDictionary[id] = post
let member = User(id: userId, username: username, email: email, profilePicture: profilePictureUrl)
if post.username == member.username {
self.userDictionary[id] = member
}
self.tableView.reloadData()
})
When you update the profile picture the DB is updated too and the above code is executed. That is why the posts are duplicated. To fix that changes the method from observe to observeSingleEvent. The observeSingleEvent method requests data from DB once.
self.ref.child("users").child(userID).observeSingleEvent(.value, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
let userId = value?["id"] as! String
let username = value?["username"] as! String
let email = value?["userEmail"] as! String
let profilePictureUrl = value?["profilePicture"] as! String
let post = Post(id: id, tags: tags, facultyName: facultyName, courseName: courseName, questionTitle: questionTitle, questionText: questionText, dateAndTime : dateAndTime, username: username)
if userID == Auth.auth().currentUser?.uid {
self.posts.insert(post, at: 0)
}
self.postDictionary[id] = post
let member = User(id: userId, username: username, email: email, profilePicture: profilePictureUrl)
if post.username == member.username {
self.userDictionary[id] = member
}
self.tableView.reloadData()
})
I tried to set up a function to listen for changes within a certain part of my database:
private func observeParticipants() {
let databaseRef = FIRDatabase.database().reference()
let groupRef = databaseRef.child("groups").child(currentRoomID).child("participants")
groupRef.observe(.childAdded, with: { snapshot in
print("observe snapshot.value: \(snapshot.value)")
if let snapDict = snapshot.value as? [String:AnyObject] {
for each in snapDict {
let uid = each.key
let avatar = each.value["profilePicture"] as! String
let gender = each.value["gender"] as! String
let handle = each.value["handle"] as! String
let name = each.value["name"] as! String
let status = each.value["status"] as! String
// Set those to the dictionaries [UID : value]
self.avatarDictionary.setValue(avatar, forKey: uid)
self.nameDictionary.setValue(name, forKey: uid)
self.genderDictionary.setValue(gender, forKey: uid)
self.handleDictionary.setValue(handle, forKey: uid)
self.statusDictionary.setValue(status, forKey: uid)
print("\n\navatarDictionary:\n \(self.avatarDictionary)")
print("\nhandleDictionary:\n \(self.handleDictionary)")
print("\ngenderDictionary:\n \(self.genderDictionary)")
print("\nnameDictionary:\n \(self.nameDictionary)")
print("\nstatusDictionary:\n \(self.statusDictionary)")
self.navBarCollectionView.reloadData()
}
}
})
}
When I run this, it loads the correct values, but if I make a change, for example add a new user to the group, it doesn't reflect that change. The database is updated fine, but the observer isn't seeing that. Is there any way I can re-write this to make is observe properly? I can post my database structure if it will help.
Thanks!
EDIT: Database structure:
https://pastebin.com/SZ2mE2Vt
EDIT: snapshot.value after adding second user:
Snapshot.value: Optional({
gender = female;
handle = sav;
name = Savina;
profilePicture = "https://graph.facebook.com/1929991317219760/picture?type=large&return_ssl_resources=1";
status = F8B016;
})
EDIT 2:
This is the code that works for fetching all user values initially, and also every time I leave the view and come back. It just doesn't act properly as an observer, i.e. if I add a participant, it doesn't see that and update the necessary values such as the collection view. I have to leave the view and come back for that to happen.
func getParticipantInfo() {
let databaseRef = FIRDatabase.database().reference()
let groupRef = databaseRef.child("groups").child(currentRoomIdGlobal)
groupRef.observe(.childAdded, with: { snapshot in
if let snapDict = snapshot.value as? [String : AnyObject] {
for each in snapDict {
let uid = each.key
let avatar = each.value["profilePicture"] as! String
let gender = each.value["gender"] as! String
let handle = each.value["handle"] as! String
let name = each.value["name"] as! String
let status = each.value["status"] as! String
// Set those to the dictionaries [UID : value]
self.avatarDictionary.setValue(avatar, forKey: uid)
self.nameDictionary.setValue(name, forKey: uid)
self.genderDictionary.setValue(gender, forKey: uid)
self.handleDictionary.setValue(handle, forKey: uid)
self.statusDictionary.setValue(status, forKey: uid)
print("\n\navatarDictionary:\n \(self.avatarDictionary)")
print("\nhandleDictionary:\n \(self.handleDictionary)")
print("\ngenderDictionary:\n \(self.genderDictionary)")
print("\nnameDictionary:\n \(self.nameDictionary)")
print("\nstatusDictionary:\n \(self.statusDictionary)")
self.navBarCollectionView.reloadData()
}
}
})
}
I'm trying to fill the collectionView with posts. I have to get the posts, then get some data for the users who posted them. For some reason it isn't working.
DataService.ds.REF_POSTS.child("\(self.loggedInUser!.uid)").queryLimitedToLast(30).observeSingleEventOfType(.Value, withBlock: { postDictionary in
if postDictionary.exists() {
if let snapshots = postDictionary.children.allObjects as? [FIRDataSnapshot] {
self.posts = [Post]()
for snap in snapshots {
if let postDict = snap.value as? NSDictionary {
for(name, value) in postDict {
let interval = postDict.objectForKey("timePosted") as! Double
let formattedDate = NSDate(timeIntervalSince1970: interval)
let timeAgo = self.getDate(formattedDate)
if name as! String == "postedBy" {
DataService.ds.REF_USERS.child(value as! String).observeSingleEventOfType(.Value, withBlock: { (userDictionary) in
let userDict = userDictionary.value as! NSDictionary
let username = userDict.objectForKey("username")!
let profileThumbUrl = userDict.objectForKey("profileThumbUrl")!
let key = snap.key
let post = Post(postKey: key, dictionary: postDict, username: username as! String, profileThumbUrl: profileThumbUrl as! String, timeAgo: timeAgo)
self.posts.append(post)
})
}
}
}
}
}
}
self.collectionView?.reloadData()
})
It works if I perform the reload() right after appending the posts, but there is some sort of memory leak. There isn't a problem in the Post class or filling the collection view, if I use dummy values. The problem is in this code that I posted. I think I have an extra loop or something can anyone help?
If you fear that reload() is the reason of the memory leak , you can use this hack:-
if name as! String == "postedBy" {
DataService.ds.REF_USERS.child(value as! String).observeSingleEventOfType(.Value, withBlock: { (userDictionary) in
let userDict = userDictionary.value as! NSDictionary
let username = userDict.objectForKey("username")!
let profileThumbUrl = userDict.objectForKey("profileThumbUrl")!
let key = snap.key
let post = Post(postKey: key, dictionary: postDict, username: username as! String, profileThumbUrl: profileThumbUrl as! String, timeAgo: timeAgo)
self.posts.append(post)
if posts.count == postDictionary.childrenCount{
self.collectionView?.reloadData()
}
})
}
Then also see this answer :- Firebase observeSingleEventOfType stays in memory