I have a node in Firebase database. its a node for the messaging portion in my App. It is a private chat. I want to incorporate the ability for one user to delete his messages but the messages still remain in the other user's app. so I created a fan out property.
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
let convoRef = FIRDatabase.database().reference().child("messages").child(convoId!)
let itemRef = rootRef.child("messages").child(convoId!).childByAutoId() // 1
let messageItem = [ // 2
"senderId": senderId!,
"ReceiverId": senderDisplayName!,
"text": text!,
"timestamp": NSDate().timeIntervalSince1970,
"convoId": convoId!
] as [String : Any]
// itemRef.setValue(messageItem) // 3
itemRef.updateChildValues(messageItem) { (error, convoRef) in
if error != nil{
print(error)
return
}
let userMessagesRef = FIRDatabase.database().reference().child("user-messages").child(self.senderId!)
let messageId = itemRef.key
userMessagesRef.updateChildValues([messageId: 1])
let userMessagesRefs = FIRDatabase.database().reference().child("user-messages").child(self.senderDisplayName!)
let messagedId = itemRef.key
userMessagesRefs.updateChildValues([messagedId: 1])
}
}
And then i created the option to delete the message cell.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
guard let uid = FIRAuth.auth()?.currentUser?.uid else {
return
}
let ref = FIRDatabase.database().reference().child("user-messages").child(uid)
ref.observe(.childAdded, with: { (snapshot) in
self.messageId = snapshot.key
FIRDatabase.database().reference().child("user-messages").child(uid).child( self.messageId!).removeValue(completionBlock: { (error, ref) in
if error != nil {
print("error \(error)")
}else{
}
})})
self.messagesDictionary.removeValue(forKey: uid)
self.handleReloadTable()
}
But when the table reloads,it crashes
func loadData()
{
guard let uid = FIRAuth.auth()?.currentUser?.uid else {
return
}
let ref = FIRDatabase.database().reference().child("user-messages").child(uid)
ref.observe(.childAdded, with: { (snapshot) in
self.messageId = snapshot.key
})
FIRDatabase.database().reference().child("messages").observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
for post in postsDictionary {
let messages = post.value as! [String: AnyObject]
for (id, value) in messages {
let info = value as! [String: AnyObject]
let convoId = info["convoId"]
let toId = info["ReceiverId"] as! String!
let fromId = info["senderId"] as! String!
if (toId == self.loggedInUserUid || fromId == self.loggedInUserUid) {
let ref = FIRDatabase.database().reference().child("messages").child(convoId as! String).child(self.messageId!)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let message = Message(dictionary: dictionary)
// self.messages.append(message)
if let receiver = message.convoId {
self.messagesDictionary[receiver] = message
self.messages = Array(self.messagesDictionary.values)
print(self.messages)
self.messages.sort(by: { (message1, message2) -> Bool in
return (message1.timestamp?.int32Value)! > (message2.timestamp?.int32Value)!
})
}
//this will crash because of background thread, so lets call this on dispatch_async main thread
DispatchQueue.main.async(execute: {
self.MessageTableView.reloadData()
})
}
}, withCancel: nil)}
}
}}})
}
And says messageId is an optional value.
Related
I am using firebase for chat app.when I try to send a message. than message, table got value but not update chat-message table. I refer to this link Here [https://github.com/DevSurya/ChatApp-Swift-And-Firebase].
when I run demo its working fine. Observe method is not calling in my case and collectionview is not refreshing
This is how I write data :
private func sendMessageWithProperty(_ property: [String: AnyObject]){
let ref = Database.database().reference().child("messages")
let childRef = ref.childByAutoId()
let toId = user!.id!
let fromId = Auth.auth().currentUser!.uid
let timeStamp = NSNumber.init(value: Date().timeIntervalSince1970)
var values: [String : AnyObject] = ["toId":toId as AnyObject, "fromId":fromId as AnyObject, "timeStamp":timeStamp]
values = values.merged(with: property)
childRef.updateChildValues(values)
childRef.updateChildValues(values) { (error, ref) in
if error != nil {
print(error!)
return
}
self.inputTextField.text = nil
let userMessageRef = Database.database().reference().child("user-messages").child(fromId).child(toId)
let messageId = childRef.key
userMessageRef.updateChildValues([messageId: 1])
let recipentUserMessageRef = Database.database().reference().child("user-messages").child(toId).child(fromId)
recipentUserMessageRef.updateChildValues([messageId: 1])
}
}
This is how I read data
func observeMessage() {
guard let uid = Auth.auth().currentUser?.uid, let toId = user?.id else {
return
}
let userMessageRef = Database.database().reference().child("user-messages").child(uid).child(toId)
userMessageRef.observe(.childAdded, with: { (snapshot) in
let messageId = snapshot.key
let messagesRef = Database.database().reference().child("messages").child(messageId)
messagesRef.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: AnyObject] else {
return
}
let message = Message()
message.setValuesForKeys(dict)
self.messages.append(message)
DispatchQueue.main.async {
self.collectionView?.reloadData()
let indexpath = NSIndexPath.init(item: self.messages.count-1, section: 0)
self.collectionView?.scrollToItem(at: indexpath as IndexPath, at: .bottom, animated: true)
}
}, withCancel: nil)
}, withCancel: nil)
}
The ObserveMessage() function get automatically called when there is an update in database.but in my case its not get called. I think the problem is with user-messages table: it is not created when I send message.
I have created a messaging system for my app and am paginating the messages within the chat log but I'm having an issue that if a new message is sent the user will have to leave the screen and re open the controller to view the new messages they have sent/received. I have tried to reload the collection view and observe the messages again with no luck. Any help is appreciated.
Observing the messages. With Pagination. (working great! On initial load.)
var messages = [Message]()
fileprivate func observeMessages() {
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let userId = user?.uid else { return }
if currentKey == nil {
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryLimited(toLast: 10).observeSingleEvent(of: .value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
let messageId = snapshot.key
let ref = Database.database().reference().child("messages").child(messageId)
ref.observe(.value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let message = Message(dictionary: dict)
self.messages.append(message)
self.messages.sort(by: { (message1, message2) -> Bool in
return message1.timeStamp.compare(message2.timeStamp) == .orderedDescending
})
self.collectionView?.reloadData()
})
})
self.currentKey = first.key
}
} else {
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryOrderedByKey().queryEnding(atValue: self.currentKey).queryLimited(toLast: 4).observeSingleEvent(of: .value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
if snapshot.key != self.currentKey {
let messageId = snapshot.key
let ref = Database.database().reference().child("messages").child(messageId)
ref.observe(.value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let message = Message(dictionary: dict)
self.messages.append(message)
self.messages.sort(by: { (message1, message2) -> Bool in
return message1.timeStamp.compare(message2.timeStamp) == .orderedDescending
})
self.collectionView?.reloadData()
})
}
})
self.currentKey = first.key
}
}
}
From Firebase database documentation
In some cases you may want a callback to be called once and then immediately removed, such as when initializing a UI element that you don't expect to change. You can use the observeSingleEventOfType method to simplify this scenario: the event callback added triggers once and then does not trigger again.
I suggest you to change to observeEventType:withBlock whichs allow you to observe all changes events.
Hope this helps.
The way I set mine up was to call the function in viewDidLoad and then again in viewDidAppear. I'm still learning as well, but you may want to try that, it would probably look something like this:
override func viewDidLoad() {
super.viewDidLoad()
observeMessages(for: userID) { (messages) in
self.messages = messages
self.collectionView.reloadData()
}
}
And again in viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
observeMessages(for: userID) { (messages) in
self.messages = messages
self.collectionView.reloadData()
}
}
For some reason when the user is receiving a message, the past messages are being duplicated. It's weird because the user who sends the messages are not having their messages duplicated when they send the message. Any help with my code would be much appreciated.
var messages = [Message]()
fileprivate func observeMessages() {
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let userId = user?.uid else { return }
if currentKey == nil {
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryLimited(toLast: 10).observe(.value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
let messageId = snapshot.key
let ref = Database.database().reference().child("messages").child(messageId)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let message = Message(dictionary: dict)
self.messages.append(message)
self.messages.sort(by: { (message1, message2) -> Bool in
return message1.timeStamp.compare(message2.timeStamp) == .orderedDescending
})
self.collectionView?.reloadData()
})
})
self.currentKey = first.key
}
} else {
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryOrderedByKey().queryEnding(atValue: self.currentKey).queryLimited(toLast: 10).observe(.value) { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
if snapshot.key != self.currentKey {
let messageId = snapshot.key
let ref = Database.database().reference().child("messages").child(messageId)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
let message = Message(dictionary: dict)
self.messages.append(message)
self.messages.sort(by: { (message1, message2) -> Bool in
return message1.timeStamp.compare(message2.timeStamp) == .orderedDescending
})
self.collectionView?.reloadData()
})
}
})
self.currentKey = first.key
}
}
}
The only places observeMessages() is being called is in willDisplay as shown below and in viewDidLoad
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if messages.count > 2 {
if indexPath.item == messages.count - 1 {
observeMessages()
}
}
}
The only difference between the user sending and user receiving message is when the user sends a message and the submit message button is pressed, I call self.messages.removeAll() and then call self.observeMessages() again which seems to work because the user who sends the message doesn't have duplicate messages.
you have used .value , instead you should use childAdded i think, then it will only get the latest added child and not the previous ones.
let userMessageRef = Database.database().reference().child("user-message").child(uid).child(userId).queryLimited(toLast: 10).observe(.childAdded) { (snapshot) in
I have a table View full of appended messages with the logged in user and other users of the app. When I swipe a cell, I want to be able to delete it from Firebase and also automatically from the tableView. I managed to get the cell deleted from Firebase but not from the tableView. This is how the messages are initially loaded into the tableView:
func loadData()
{
guard let uid = FIRAuth.auth()?.currentUser?.uid else {
return
}
FIRDatabase.database().reference().child("messages").observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
for post in postsDictionary {
let messages = post.value as! [String: AnyObject]
for (id, value) in messages {
let info = value as! [String: AnyObject]
let convoId = info["convoId"]
let toId = info["ReceiverId"] as! String!
let fromId = info["senderId"] as! String!
if (toId == self.loggedInUserUid || fromId == self.loggedInUserUid) {
let refs = FIRDatabase.database().reference().child("user-messages").child(convoId as! String).child(uid)
refs.observe(.childAdded, with: { (snapshot) in
self.messageId = snapshot.key
let ref = FIRDatabase.database().reference().child("messages").child(convoId as! String).child(self.messageId!)
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let message = Message(dictionary: dictionary)
if let receiver = message.convoId {
self.messagesDictionary[receiver] = message
self.messages = Array(self.messagesDictionary.values)
print(self.messages)
self.messages.sort(by: { (message1, message2) -> Bool in
return (message1.timestamp?.int32Value)! > (message2.timestamp?.int32Value)!
})
}
//this will crash because of background thread, so lets call this on dispatch_async main thread
DispatchQueue.main.async(execute: {
self.MessageTableView.reloadData()
})
}
}, withCancel: nil) })}
}
}}})
}
Here is how I perform the delete function in Firebase and attempt to perform a delete function in the tableView:
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
guard let uid = FIRAuth.auth()?.currentUser?.uid else {
return
}
let message = messages[(indexPath.row)]
self.deletemessage = message.convoId
FIRDatabase.database().reference().child("messages").observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
for post in postsDictionary {
let messages = post.value as! [String: AnyObject]
for (id, value) in messages {
let info = value as! [String: AnyObject]
let convoId = info["convoId"]
let ref = FIRDatabase.database().reference().child("user-messages").child(self.deletemessage!).child(uid)
ref.observe(.childAdded, with: { (snapshot) in
self.messageId = snapshot.key
FIRDatabase.database().reference().child("user-messages").child(self.deletemessage!).child(uid).child( self.messageId!).removeValue(completionBlock: { (error, ref) in
if error != nil {
print("error \(error)")
}else{
}})})}}}})
self.messages.remove(at: indexPath.row)
self.MessageTableView.deleteRows(at: [indexPath], with: .automatic)
}
}
it deletes from the tableView but crashes and gives me an error on self.messages.remove(at: indexPath.row) fatal error: Index out of Range.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let state = id[indexPath.row] //it store the child key to be removed. here id array is not displayed but used as backend process
print(state) // (Not required)
names.remove(at: indexPath.row) //Remove the selected name from array used in TableView that is displayed in cell
tableView.deleteRows(at: [indexPath], with: .fade) // TableView Animation
Database.database().reference().child("artists").child(id[indexPath.row]).removeValue() // remove the child referred using id from database
id.remove(at: indexPath.row) // removing selected if from id array locally
}
}
Try the following code In your case please and let me know issue still Exists
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
guard let uid = FIRAuth.auth()?.currentUser?.uid else {
return
}
let message = messages[(indexPath.row)]
self.deletemessage = message.convoId
FIRDatabase.database().reference().child("messages").observeSingleEvent(of: .value, with: { (snapshot:FIRDataSnapshot) in
if let postsDictionary = snapshot .value as? [String: AnyObject] {
for post in postsDictionary {
let messages = post.value as! [String: AnyObject]
for (id, value) in messages {
let info = value as! [String: AnyObject]
let convoId = info["convoId"]
let ref = FIRDatabase.database().reference().child("user-messages").child(self.deletemessage!).child(uid)
ref.observe(.childAdded, with: { (snapshot) in
self.messageId = snapshot.key
FIRDatabase.database().reference().child("user-messages").child(self.deletemessage!).child(uid).child( self.messageId!).removeValue(completionBlock: { (error, ref) in
if error != nil {
print("error \(error)")
}
})
})
}
}}})
self.messages.remove(at: indexPath.row)
self.MessageTableView.deleteRows(at: [indexPath], with: .automatic)
}
}
Try this it occur because you are deleting value correct but under a handler which even caused a error in my project . data in messages is loaded from database so if value is not delete from database it will update again even if u remove from array. so please try this code
I followed the Firebase tutorial by Ray Wenderlich (Link) and adopted his way of initializing the object (in my case of type "Location") with the snapshot from the observe-method:
class Location:
init(snapshot: FIRDataSnapshot) {
identifier = snapshot.key
let snapshotValue = snapshot.value as! [String : AnyObject]
type = snapshotValue["type"] as! String
name = snapshotValue["name"] as! String
address = snapshotValue["address"] as! String
latitude = Double(snapshotValue["latitude"] as! String)!
longitude = Double(snapshotValue["longitude"] as! String)!
avatarPath = snapshotValue["avatarPath"] as! String
ref = snapshot.ref
}
LocationsViewController:
databaseHandle = locationsRef?.queryOrdered(byChild: "name").observe(.value, with: { (snapshot) in
var newLocations:[Location] = []
for loc in snapshot.children {
let location = Location(snapshot: loc as! FIRDataSnapshot)
newLocations.append(location)
}
self.locations = newLocations
self.tableView.reloadData()
})
This really works like a charm, but now I'm trying to load the image stored under the storage reference "avatarPath".
My attempt worked but the images take a ling time to load. Is there a better way/place to load these images?
My attempt 1:
databaseHandle = locationsRef?.queryOrdered(byChild: "name").observe(.value, with: { (snapshot) in
var newLocations:[Location] = []
for loc in snapshot.children {
let location = Location(snapshot: loc as! FIRDataSnapshot)
newLocations.append(location)
}
self.locations = newLocations
self.tableView.reloadData()
//Load images
for loc in self.locations {
let imagesStorageRef = FIRStorage.storage().reference().child(loc.avatarPath)
imagesStorageRef.data(withMaxSize: 1*1024*1024, completion: { (data, error) in
if let error = error {
print(error.localizedDescription)
} else {
loc.avatarImage = UIImage(data: data!)!
self.tableView.reloadData()
}
})
}
})
My 2nd Attempt (inside Location class):
init(snapshot: FIRDataSnapshot) {
identifier = snapshot.key
let snapshotValue = snapshot.value as! [String : AnyObject]
type = snapshotValue["type"] as! String
name = snapshotValue["name"] as! String
address = snapshotValue["address"] as! String
latitude = Double(snapshotValue["latitude"] as! String)!
longitude = Double(snapshotValue["longitude"] as! String)!
avatarPath = snapshotValue["avatarPath"] as! String
ref = snapshot.ref
super.init()
downloadImage()
}
func downloadImage() {
let imagesStorageRef = FIRStorage.storage().reference().child(self.avatarPath)
imagesStorageRef.data(withMaxSize: 1*1024*1024, completion: { (data, error) in
if let error = error {
print(error.localizedDescription)
} else {
self.avatarImage = UIImage(data: data!)!
}
})
}
Thank you in advance!
Nico
The best way you can accomplish that is to load asynchronous inside the loading of the cell function. I mean:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
DispatchQueue.main.async {
let imagesStorageRef = FIRStorage.storage().reference().child(self.locations[indexPath.row].avatarPath)
imagesStorageRef.data(withMaxSize: 1*1024*1024, completion: { (data, error) in
if let error = error {
print(error.localizedDescription)
} else {
locations[indexPath.row].avatarImage = UIImage(data: data!)!
tableView.reloadRows(at indexPaths: [indexPath], with animation: .none)
}
})
}
}
In first attempt try changing your code as:
DispatchQueue.main.async {
for loc in self.locations {
let imagesStorageRef = FIRStorage.storage().reference().child(loc.avatarPath)
imagesStorageRef.data(withMaxSize: 1*1024*1024, completion: { (data, error) in
if let error = error {
print(error.localizedDescription)
} else {
loc.avatarImage = UIImage(data: data!)!
self.tableView.reloadData()
}
})
}
}