Delete cell from UITableView and Firebase - ios

I try to delete cell (left swipe) and remove data from Firebase Database and Storage. Using this code:
struct Records {
let title: String
let source: String
var length: String
let recordUrl: String
let username: String
init(dictionary: [String: Any]) {
self.title = dictionary["title"] as? String ?? ""
self.source = dictionary["source"] as? String ?? ""
self.length = dictionary["length"] as? String ?? ""
self.recordUrl = dictionary["recordUrl"] as? String ?? ""
self.username = dictionary["username"] as? String ?? ""
}
}
I've got a var
var records = [Records]()
Trying to delete cell
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
guard let uid = Auth.auth().currentUser?.uid else { return }
if (editingStyle == .delete) {
records.remove(at: indexPath.item)
tableView.deleteRows(at: [indexPath], with: .fade)
Database.database().reference().child("users").child(uid).child("records").removeValue()
}
}
Firebase JSON enter image description here
I need to delete item inside "records"(appears as cell in my TableView), but now using my code I delete whole "records"

You need to save the ID for the records too. This way you can create a path to that particular record. After that just add one more child in the path.
Like this.
Database.database().reference().child("users").child(uid).child("records").child(record.id).removeValue()

Got some new. Can get key of all items in branch "records"
guard let uid = Auth.auth().currentUser?.uid else { return }
let ref = Database.database().reference().child("users").child(uid).child("records")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionaries = snapshot.value as? [String: Any] else { return }
let record = self.records[indexPath.item]
dictionaries.forEach({ (key, value) in
print("Key \(key)")
but still can't get an id to delete of current item (record in branch "records")

Related

Firebase Realtime database - tableview update all the cell data if new data added

I am working on a group chat app and trying to fetch the last message from a node to display the last message under the group name. Everything is working fine. When the group receives a new message, the last message is showing in the correct group and some other random groups as well. If I open the correct group and come back, all the other groups are showing the correct last message. Kindly help me with the below code.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return groupList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:GroupsTableViewCell = tableView.dequeueReusableCell(withIdentifier: "GroupsTableViewCell") as! GroupsTableViewCell
cell.selectionStyle = .none
var user: Groupslist
if groupList.count > 0 {
user = groupList[indexPath.row]
cell.lbl_name_group.text = user.name;
cell.view_count.isHidden = true
GetLastMsg(groupID: user.group_id ?? "", cell: cell)
cell.img_group.image = UIImage.init(named:"final_grp")
GetUnReadMsgCount(groupID: user.group_id ?? "", cell: cell)
}
return cell
}
groupList contains the list of groups which is fetched from firebase on viewWillAppear.
func GetLastMsg(groupID : String, cell : GroupsTableViewCell){
let userRef2 = rootRef2.child("message").child(groupID).queryLimited(toLast: 1)
userRef2.observe( .value, with: { snapshot in
guard let _ = snapshot.value as? [String:AnyObject] else {
print("Error")
return
}
print("children count: \(snapshot.children.allObjects.count)")
for snap in snapshot.children.allObjects as! [DataSnapshot] {
let message = Message()
let value = snap.value as? [String: Any] ?? [:]
let text = value["text"] as? String ?? "Name not found"
let mimType = value["mimType"] as? String
let name_doc = value["name_doc"] as? String ?? "file.file"
message.text = text
message.mimType = mimType
message.name_doc = name_doc
message.timestamp_group = (value["timestamp"] as! NSNumber)
print(value["idSender"] as? String)
print(message.name_doc)
var msg_last = ""
// cell.lbl_lastmsg.text = msg_last
if message.mimType == "image/jpeg" {
msg_last = "Image"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "im")
}else if message.mimType == "audio/3gpp" {
msg_last = "Audio"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "mi")
}else if message.mimType == "video/mp4" {
msg_last = "Video"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "vi")
}else if message.mimType == "application/pdf" {
let name_docs = message.name_doc!.split{$0 == "."}.map(String.init)
msg_last = name_docs.last!
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "doc")
}else if mimType == nil{
msg_last = message.text!
cell.img_small_width.constant=0
}
DispatchQueue.main.async {
cell.lbl_lastmsg.text = msg_last
}
}
}
})
}
New method:
func GetUnReadMsgCount(groupID : String, cell : GroupsTableViewCell) {
rootRef2.child("group_message_unread").child(current_user!).child(groupID).observe(.childAdded, with: { (snapshot) -> Void in
print(snapshot.childrenCount)
print(snapshot.children.description)
if snapshot.childrenCount > 0 {
DispatchQueue.main.async {
cell.view_count.isHidden = false
cell.lbl_count.text = String(snapshot.childrenCount)
// self.tbl_groups.reloadRows(at: [IndexPath(row: 3, section: 0)], with: .fade)
// self.tbl_groups.reloadData()
}
}
})
}
That's the expected behavior. In a .observe( .value listener, the snapshot is always the complete data at the path. So when you add a child node, the snapshot fires with the entire new data at userRef2.
You have two main options to not get duplicates in your UI:
Clear all messages when there is an update, before adding the data to the table again. So that'd be right before for snap in snapshot.children in your code. This is by far the simplest way to solve the problem if duplicate rows, but may lead to some flashing in your UI.
Listen for child events, instead of observing the entire value. With this you'll get a single .childAdded event for the new child, you can get rid of the for snap in snapshot.children.allObjects and just add the singular new child node to the table.

how to delete UITableView Cell after document gets removed from Firestore console

since of starting my project now i didn't find any answer about how to delete the UITableView cell from an outside source, I'm struggling here about how to delete UITableview Rows or Cells after it gets deleted from My FireStore Cloud Storage, I want to remove the value from the array and delete that cell without using canEditRowAt indexPath cause I want to delete it in
one more thing I don't know how to use dictionaries with arrays, and I don't know if I have to use it in such a case
struct accepteddriverData {
var driverName: String
var userPhone: String
var username: String
var driverPhone: String
}
func driverSelected(arg: Bool, completion: (Bool) -> ()){
guard let driverid = Auth.auth().currentUser?.uid else {return}
let firestore = Firestore.firestore()
let doc = firestore.collection("offers").whereField("driverUid", isEqualTo: driverid).whereField("selected", isEqualTo: true)
doc.addSnapshotListener { (querysnap, err) in
if err != nil {
print(err?.localizedDescription ?? "")
}
querysnap?.documentChanges.forEach({ (change) in
self.DriverData = []
if (change.type == .added) {
let snap = change.document.data()
let driverName = snap["driverName"] as? String ?? ""
let userPhone = snap["userPhone"] as? String ?? ""
let username = snap["userName"] as? String ?? ""
let driverPhone = snap["driverPhone"] as? String ?? ""
let accepted = accepteddriverData(driverName: driverName, userPhone: userPhone, username: username, driverPhone: driverPhone)
self.DriverData.append(accepted)
DispatchQueue.main.async {
self.DriverOrdersTV.reloadData()
}
}
if (change.type == .removed){ // i want to delete tableView cell on this statement
let id = change.document.documentID
let snap = change.document.data()
print(id)
print(snap)
}
})
}
completion(arg)
}
Add to your model
var docId: String
then inside
if (change.type == .removed){
let id = change.document.documentID
self.DriverData.removeAll(where:{ $0.docId == id })
// refresh table
}

Firebase - sort posts by number of keys and show in TableView

I have made app that save users message and its username to Firebase with childByAutoID key. Inside there are childs that saves Username, message, likes and PostID (as you can see on image below).
After a lot of research and a lot of trying writing code by myself, I figure out that likes need to be saved as autoID keys inside separate child and then you have to count that keys to get number of likes (you'll see that child also on image under and that child is named "Liked")
Everything is displayed in tableView cell.
But all of them are displayed randomly which is OK (I would prefer to be ordered by date added), but what would really like is that loaded data in next next VC to be displayed as:
TOP 10 of the week
TOP 10 of the month
ALL BEST etc...
There'll be separate button for menu and when you press it, you'll be presented with next VC that contain Table View with same data, but this time sorted by most liked post.
This is code that writes keys to LIKED child path (when like is pressed on already loaded data from Firebase database):
#IBAction func likePressed(_ sender: Any) {
let ref = Database.database().reference()
self.likeButton.isEnabled = false
let key = ref.child("Frusters").childByAutoId().key
ref.child("Frusters").child(self.postID).observeSingleEvent(of: .value, with: { (snapshot) in
let updateLikes = ["Liked/\(key)" : key] as [String : Any]
ref.child("Frusters").child(self.postID).updateChildValues(updateLikes, withCompletionBlock: { (error, reff) in
if error == nil {
ref.child("Frusters").child(self.postID).observeSingleEvent(of: .value, with: { (snap) in
if let properties = snap.value as? [String : AnyObject] {
if let likes = properties["Liked"] as? [String : AnyObject] {
let count = likes.count
self.likeLabel.text = "\(count) Likes"
let update = ["likes" : count]
ref.child("Frusters").child(self.postID).updateChildValues(update)
self.likeButton.isHidden = true
self.unlikeButton.isHidden = false
self.likeButton.isEnabled = true
}
}
})
}
})
})
ref.removeAllObservers()
}
and this is the code that loads data and put it in my table view:
func loadData() {
self.fetchPosts.removeAll()
let ref = Database.database().reference()
ref.child("Frusters").observeSingleEvent(of: .value, with: { (snapshot) in
if let postDict = snapshot.value as? [String:AnyObject] {
for (_,postElement) in postDict {
print(postElement);
let post = Post()
post.username = postElement["Username"] as? String
post.message = postElement["Message"] as? String
post.likes = postElement["likes"] as? Int
post.postID = postElement["postID"] as? String
self.fetchPosts.append(post)
}
}
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
ref.removeAllObservers()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.fetchPosts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! PostTableViewCell
cell.messageLabel.text = self.fetchPosts[indexPath.row].message
cell.usernameLabel.text = self.fetchPosts[indexPath.row].username
cell.likeLabel.text = "\(self.fetchPosts[indexPath.row].likes!) Likes"
cell.postID = self.fetchPosts[indexPath.row].postID
cell.bckgView.layer.cornerRadius = 0
cell.bckgView.layer.shadowOffset = CGSize(width: 0, height: 1)
cell.bckgView.layer.masksToBounds = false
cell.bckgView.layer.shadowColor = UIColor.black.cgColor
cell.bckgView.layer.shadowOpacity = 0.3
cell.bckgView.layer.shadowRadius = 4
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension;
}
Well, problem is that I do not know how to insert inside my new UITableView top 10 post with most likes, to sort them from most liked post to next 9 of them with most likes.
Also, is it possible to sort them most liked this month or this week?
Does this keys that Firebase database makes (by autoID) contain date of created post or do I have to insert new child with date inside and then in code combine "date" child and "liked" child to be presented as top 10 liked post between 1st and last of this month?
Thanks in advance. ;)
1-You don't have to store each like separately, if all you care about is the number. You can just update the number.
#IBAction func likePressed(_ sender: Any) {
let ref = Database.database().reference()
self.likeButton.isEnabled = false
let key = ref.child("Frusters").childByAutoId().key
ref.child("Frusters").child(self.postID).observeSingleEvent(of: .value, with: { (snapshot) in
let counted = snapshot.value as? Int
self.ref.child("Flusters".child(self.postID).child("likes").setValue(counted! + 1)
})
2- Yes, you can sort by likes. you'd need to use the .queryOrdered function. Update the code as follows
func loadData() {
self.fetchPosts.removeAll()
let ref = Database.database().reference()
ref.child("Frusters").queryOrdered(byChild: "likes").observeSingleEvent(of: .value, with: { (snapshot) in
if let postDict = snapshot.value as? [String:AnyObject] {
for (_,postElement) in postDict {
print(postElement);
let post = Post()
post.username = postElement["Username"] as? String
post.message = postElement["Message"] as? String
post.likes = postElement["likes"] as? Int
post.postID = postElement["postID"] as? String
self.fetchPosts.append(post)
}
}
self.tableView.reloadData()
3- To order by top week, month, you'd have to keep track of a timestamp.

Merging Firebase Data into Table View

To start off I have 2 sets of data stored in Firebase. I will include both sets in a photo.
This first photo shows where I keep all the user's profiles.
When someone checks in on the app I store their username and check-in time at a different place.
In the photo above you will be able to see that there are currently 2 users checked in on "12-3-2017 Block 2"
I have no problem storing the data.
I do however have problems displaying the data.
I want to be able to create a UITableView that shows all the users who are checked in and the time they checked in while in the same list show all the users who have yet to checkin.
import UIKit
import Firebase
struct checkInStruct {
let userName : String!
let hour : String!
let minutes : String!
let userNameFromAllUsers : String!
}
class checkInDaysTableViewController: UITableViewController {
//Variables
var lastChildNameSegue = ""
var posts = [checkInStruct]()
let user = FIRAuth.auth()?.currentUser
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "\(lastChildNameSegue)"
print("Name of Child After Segue -> \(lastChildNameSegue)")
loadCheckedInUsers()
loadAllUsers()
print(posts)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
let userName = posts[indexPath.row].userName
let hour = posts[indexPath.row].hour
var minute = posts[indexPath.row].minutes
let userNameOfAllUsers = posts[indexPath.row].userNameFromAllUsers
let label1 = cell?.viewWithTag(1) as! UILabel
let label2 = cell?.viewWithTag(2) as! UILabel
if(hour == nil || hour == ""){
//Users isnt signed in
label1.text = "\(userName!)"
label2.text = "User Not checked in"
}else{
label1.text = "\(userName!)"
label2.text = "\(hour!):\(minute!)"
}
return cell!
}
func loadCheckedInUsers (){
let databaseRef = FIRDatabase.database().reference()
let userID = user?.uid
databaseRef.child("users").child("\(userID!)").child("checkins").child("\(lastChildNameSegue)").queryOrderedByKey().observe(.childAdded, with: { (snapshot) in
let userNameSnapValue = snapshot.value as? NSDictionary
let userName = userNameSnapValue?["userName"] as? String
let hourSnapValue = snapshot.value as? NSDictionary
let hour = hourSnapValue?["hour"] as? String
let minutesValue = snapshot.value as? NSDictionary
let minutes = minutesValue?["minutes"] as? String
print(userName!)
print(hour!)
print(minutes!)
self.posts.insert(checkInStruct(userName: userName, hour: hour, minutes: minutes, userNameFromAllUsers: userName) , at: 0)
self.tableView.reloadData()
})
}
func loadAllUsers(){
let databaseRef = FIRDatabase.database().reference()
let userID = user?.uid
databaseRef.child("users").child("\(userID!)").child("employeesorstudents").queryOrderedByKey().observe(.childAdded, with: { (snapshot) in
let userNameSnapValue = snapshot.value as? NSDictionary
let userName = userNameSnapValue?["userName"] as? String
let hour = ""
let minutes = ""
print(userName!)
self.posts.insert(checkInStruct(userName: userName, hour: hour, minutes: minutes, userNameFromAllUsers: userName) , at: 0)
self.tableView.reloadData()
})
}
}
As you can see I am storing data in one struct.
However, on the app, it is showing both the name of the user from the database of all users and the checked in users.
In the screenshot, you will see "Brandon Happy" has a check-in time and then he reappears again saying he was never checked in. How can I delete the duplicate data from the struct?
Everything looks good however what you should do is not add data to the struct if it already exists so what you need to do is fix your loadAllUsers() function.
func loadAllUsers(){
let databaseRef = FIRDatabase.database().reference()
let userID = user?.uid
databaseRef.child("users").child("\(userID!)").child("employeesorstudents").queryOrderedByKey().observe(.childAdded, with: { (snapshot) in
let userNameSnapValue = snapshot.value as? NSDictionary
let userName = userNameSnapValue?["userName"] as? String
let hour = ""
let minutes = ""
print(userName!)
databaseRef.child("users").child("\(userID!)").child("checkins").child("\(self.lastChildNameSegue)").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.hasChild("\(userName!)"){
print("User already checked in")
}else{
print("user has not yet checked in")
self.posts.insert(checkInStruct(userName: userName, hour: hour, minutes: minutes, userNameFromAllUsers: userName) , at: 0)
self.tableView.reloadData()
}
})
})
}
In the if statement it will figure out if the user has or has not been checked in yet and then adds it to the struct if they have not been checked in.

Firebase query sort order in swift?

When I load the following Firebase Database data into my tableView, the data is sorted in ascending order by date. How can I order this by descending (show the newest post at the top)?
Query in Xcode:
let ref = self.rootRef.child("posts").queryOrderedByChild("date").observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
JSON export:
"posts" : {
"-KMFYKt7rmfZINetx1hF" : {
"date" : "07/09/16 12:46 PM",
"postedBy" : "sJUCytVIWmX7CgmrypqNai8vGBg2",
"status" : "test"
},
"-KMFYZeJmgvmnqZ4OhT_" : {
"date" : "07/09/16 12:47 PM",
"postedBy" : "sJUCytVIWmX7CgmrypqNai8vGBg2",
"status" : "test"
},
Thanks!!
EDIT: Below code is the entire solution thanks to Bawpotter
Updated query:
let ref = self.rootRef.child("posts").queryOrderedByChild("date").observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
let post = Post.init(key: snapshot.key, date: snapshot.value!["date"] as! String, postedBy: snapshot.value!["postedBy"] as! String, status: snapshot.value!["status"] as! String)
self.posts.append(post)
self.tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: self.posts.count-1, inSection: 0)], withRowAnimation: .Automatic)
tableView cellForRowAtIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PostCell", forIndexPath: indexPath) as! PostCell
self.posts.sortInPlace({$0.date > $1.date})
self.tableView.reloadData()
Post.swift:
import UIKit
class Post {
var key: String
var date: String
var postedBy: String
var status: String
init(key: String, date: String, postedBy: String, status: String){
self.key = key
self.date = date
self.postedBy = postedBy
self.status = status
}
}
When Firebase loads the data into your tableView data source array, call this:
yourDataArray.sortInPlace({$0.date > $1.date})
Swift 3 Version:
yourDataArray.sort({$0.date > $1.date})
Swift 4 Version:
yourDataArray.sort(by: {$0.date > $1.date})
While I do recommend doing what was posted and creating a Class and doing it that way, I will give you another way to do sort it.
Since you already have it sorted in Ascending order from Firebase and you know the amount of records, you can do this:
guard let value = snapshot.children.allObjects as? [FIRDataSnapshot] else {
return
}
var valueSorted: [FIRDataSnapshot] = [FIRDataSnapshot]()
var i: Int = value.count
while i > 0 {
i = i - 1
valueSorted.append(value[i])
}
Simply use .reverse() before using data
Example:
myRef.observe(.value) { (snapshot) in
guard var objects = snapshot.children.allObjects as? [DataSnapshot] else {
return
}
objects.reverse() //<========= HERE
//Now use your `objects`
...
}
Swift 5:
Add reversed() to your objects after sorting by the required field.
For example, let's assume you have a day of the month in the "day" field in FireStore.
Something like this will do the trick (call loadData() function in viewDidLoad to see the output):
let db = Firestore.firestore()
func loadData() {
db.collection("FireStoreCollectionName").order(by: "day").getDocuments { (querySnapshot, error) in
if let e = error {
print("There was an issue retrieving data from Firestore, \(e)")
} else {
for document in querySnapshot!.documents.reversed() {
let data = document.data()
let fDay = data["day"] as! Int
print(fDay)
}
}
}
}

Resources