How to delete tableview items after they are deleted in Firebase - ios

My tableview currently updates my table and adds new items in real-time when they are added to my firebase database. The problem is that I cannot delete in real-time. I am storing my data from firebase in a local array, and then loading that array to the tableview.
I tried to condense my code a bit. I also tried to put the Firebase code that is inside my removeDeletedItems() function inside my populateArrays() function, and to put it after the .childAdded listener, but did not have luck with deleting the data in real-time.
override func viewDidLoad() {
super.viewDidLoad()
populateArrays()
}
func removeDeletedItems() {
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Users").observe(FIRDataEventType.childRemoved, with: { (FIRDataSnapshot) in
guard let emailToFind = FIRDataSnapshot.value as? String else { return }
for (index, email) in self.usernames.enumerated() {
if email == emailToFind {
let indexPath = IndexPath(row: index, section: 0)
self.usernames.remove(at: index)
self.tableView.deleteRows(at: [indexPath], with: .fade)
self.tableView.reloadData()
}
}
})
}
func populateArrays(){
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Users").observe(FIRDataEventType.childAdded, with: { (FIRDataSnapshot) in
if let data = FIRDataSnapshot.value as? NSDictionary {
if let name = data[Constants.NAME] as? String {
self.usernames.append(name)
self.removeDeletedItems()
self.tableView.reloadData()
}
}
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = usernames[indexPath.row]
return cell
}

Isn't the observed value always a dictionary? And shouldn't you check also for the name rather than the email?
The loop to find the name is not needed. There is a convenience function.
databaseRef.child("Users").observe(FIRDataEventType.childRemoved, with: { snapshot in
guard let data = snapshot.value as? [String:Any],
let nameToFind = data[Constants.NAME] as? String else { return }
if let index = self.usernames.index(of: nameToFind) {
let indexPath = IndexPath(row: index, section: 0)
self.usernames.remove(at: index)
self.tableView.deleteRows(at: [indexPath], with: .fade)
// don't reload the table view after calling `deleteRows`
}
}
})

Related

Animating tableview cell in swift

I want to animate table view cell during deleting the row.I am trying the following code, but the cell animation is not very good. How could i improve the animatin please help.
My code is:
#objc func userLikeButtonWasTappaed(sender: UIButton){
guard let indexPath = tableView.indexPathForRow(at: sender.convert(sender.frame.origin, to: tableView)) else {
return
}
let cell = tableView.cellForRow(at: indexPath) as? MatchingUsersTVCell
let tag = sender.tag
if modelNameArray.count > 0{
let userid = userIdArray[tag]
totalScoreArray.remove(at: tag)
modelNameArray.remove(at: tag)
self.tableView.beginUpdates()
UIView.animate(withDuration: 1) {
self.tableView.deleteRows(at: [indexPath], with: .right)
}
self.tableView.endUpdates()
let uid: Int = UserDefaults.standard.value(forKey: "User_Id") as! Int
let accessToken: String = UserDefaults.standard.value(forKey: "access_token") as! String
apiRequest.likeTheUser(uid, userid, accessToken) { (likedUser) in
}
}
}

Update that object with the new info in array and to display tableview Swift

I am using firebase realtime database and implementing user profile data with usersFriend and location. I need to implement the update in object array and show updated values in tableview. I have tried but I am not successful in updating object and then tableview reload. Function already developed.
I need to show updated object array swapped with new values and display in tableview.
var myFriendsDataSource = [FriendClass]()
func watchForChangesInMyFriends() {
let usersRef = self.ref.child("profiles") usersRef.observe(.childChanged, with: { snapshot in
let key = snapshot.key
if let friendIndex = self.myFriendsDataSource.firstIndex(where: { $0.uid == key} ) {
let friend = self.myFriendsDataSource[friendIndex]
print("found user \(friend.batteryStatus), updating")
self.myFriendsDataSource[friendIndex] = friend
self.tableView.reloadData()
}
})
}
Class:
class FriendClass {
var uid = ""
var name = ""
var batteryStatus = Int()
var latitude = Double()
var longitude = Double()
var timeStamp = Int64()
//var profilePic
init(withSnapshot: DataSnapshot) {
self.uid = withSnapshot.key
self.name = withSnapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
self.batteryStatus = withSnapshot.childSnapshot(forPath: "batteryStatus").value as? Int ?? 0
self.latitude = withSnapshot.childSnapshot(forPath: "latitude").value as? Double ?? 0.0
self.longitude = withSnapshot.childSnapshot(forPath: "longitude").value as? Double ?? 0.0
self.timeStamp = withSnapshot.childSnapshot(forPath: "timeStamp").value as? Int64 ?? 0
}
}
Updated:
func loadUsersFriends() {
let uid = "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
let myFriendsRef = self.ref.child("userFriends").child(uid)
myFriendsRef.observeSingleEvent(of: .value, with: { snapshot in
let uidArray = snapshot.children.allObjects as! [DataSnapshot]
for friendsUid in uidArray {
self.loadFriend(withUid: friendsUid.key)
print(friendsUid)
}
})
}
func loadFriend(withUid: String) {
let thisUserRef = self.ref.child("profiles").child(withUid)
thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
let aFriend = FriendClass(withSnapshot: snapshot)
self.myFriendsDataSource.append(aFriend)
print(self.myFriendsDataSource)
self.tableView.reloadData()
self.watchForChangesInMyFriends()
})
}
Update 2:
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 10
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myFriendsDataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FriendListTableViewCell", for: indexPath) as! FriendListTableViewCell
let dic = myFriendsDataSource[indexPath.row]
cell.frndName.text = dic.name
return cell
}
Given the above comment discussion, I think you need to update your watchForChangesInMyFriends method as below to actually update the datasource with the new friend data. You should also do all your UI updates on the main thread, and as there is no guarantee that this closure will run on the main thread you need to force the tableView update onto the main thread.
func watchForChangesInMyFriends() {
let usersRef = self.ref.child("profiles") usersRef.observe(.childChanged, with: { snapshot in
let key = snapshot.key
if let friendIndex = self.myFriendsDataSource.firstIndex(where: { $0.uid == key} ) {
let friend = self.myFriendsDataSource[friendIndex]
print("found user \(friend.batteryStatus), updating")
self.myFriendsDataSource[friendIndex] = FriendClass(withSnaphot: snapshot)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
It's also better practice to update just the tableView data that has changed rather than reloading the whole tableView. You can probably use the array index to generate an IndexPath for the appropriate row and then just reload that row. Without seeing your tableView methods I can't be precise, but it'll probably look something like this:
let indexPath = IndexPath(row: friendIndex, section: 0)
DispatchQueue.main.async {
self.tableView.reloadRows(at: [indexPath], with: .automatic)
}

How can I run a function before my extension code in Swift?

I am working on a project and am using a tableView to load data. The issue is that I need the number of cells to be determined by a specific function. My tableView sets the number of cells in an extension I added so no matter where I call the function, it still runs second. Any help would be much appreciate, here is my code (the function and the extension):
func setNumCells() {
let uid = Auth.auth().currentUser?.uid
var ref: DatabaseReference!
ref = Database.database().reference()
let applicationReference = ref.child("applications")
ref.child("applications").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
print("So far")
let array = Array(dictionary.keys)
print(array)
for i in 0..<array.count {
ref.child("applications").child(uid!).child(String(array[i])).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let array = Array(dictionary.keys)
self.numApplications += array.count - 1
}
})
}
}
})
}
...
extension ApplicationViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numApplications
}
func tableView(_ tableView: UITableView, cellForRowAt inde xPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.backgroundColor = UIColor.red
tableView.rowHeight = 85
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
}
You have to call reloadData on the table view and on the main thread after receiving all data
The recommended API to handle the timing is DispatchGroup
func setNumCells() {
let uid = Auth.auth().currentUser?.uid
var ref: DatabaseReference!
ref = Database.database().reference()
let applicationReference = ref.child("applications")
let group = DispatchGroup()
ref.child("applications").child(uid!).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
print("So far")
let array = Array(dictionary.keys)
print(array)
for item in array {
group.enter()
ref.child("applications").child(uid!).child(String(item)).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let array = Array(dictionary.keys)
self.numApplications += array.count - 1
}
group.leave()
})
}
group.notify(queue: DispatchQueue.main) {
self.tableView.reloadData()
}
}
})
}
Notes:
for i in 0..<array.count is horrible as the index is actually not needed. See my improved code.
Never create table view cells with the default initializer. Reuse them.
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

refresh tableView after deleting cell

I have a tableView with data populated from Firebase, when I click a delete button the data is removed from Firebase but it remains on my app and it doesn't remove the data from the tableView until I close the app and reopen it. Here is how I set up the delete function:
func deletePost() {
let uid = FIRAuth.auth()!.currentUser!.uid
let storage = FIRStorage.storage().reference(forURL: "gs://gsignme-14416.appspot.com")
FIRDatabase.database().reference().child("posts").child(uid).observe(.childAdded, with: { (snapshot) in
let indexPath = self.selectedIndex
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
self.key = post["postID"] as? String
self.itemsRef = FIRDatabase.database().reference().child("posts").child(uid).child(self.key!)
// Remove the post from the DB
FIRDatabase.database().reference().child("books").child(self.key!).removeValue { error in
if error != nil {
print("error \(error)")
}
}
})
self.TableView.reloadData()
}
Here are the delegate and datasource:
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
posts.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectedIndex = indexPath
self.didExpandCell()
if isExpanded && self.selectedIndex == indexPath{
print(indexPath)
} else{
}}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if isExpanded && self.selectedIndex == indexPath{
return 300
}
return 126
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cells", for: indexPath) as! ProfileTableViewCell
//Configure the cell
let post = self.posts[indexPath.row] as! [String: AnyObject]
cell.Title.text = post["title"] as? String
cell.Author.text = post["Author"] as? String
cell.ISBN10.text = post["ISBN10"] as? String
return cell
}
I attempted to add a tableview.reloaddata at the end of the function but that doesn't help. What am I doing wrong?
Remove your object from posts array and then reload your tableView in main queue one you remove your object from firebase.
Check below code:
func deletePost() {
let uid = FIRAuth.auth()!.currentUser!.uid
let storage = FIRStorage.storage().reference(forURL: "gs://gsignme-14416.appspot.com")
FIRDatabase.database().reference().child("posts").child(uid).observe(.childAdded, with: { (snapshot) in
let indexPath = self.selectedIndex
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
self.key = post["postID"] as? String
self.itemsRef = FIRDatabase.database().reference().child("posts").child(uid).child(self.key!)
// Remove the post from the DB
FIRDatabase.database().reference().child("books").child(self.key!).removeValue { error in
if error != nil {
print("error \(error)")
} else {
//Here remove your object from table array
self.posts.remove(at: indexPath?.row)
//Reload your tableview in main queue
DispatchQueue.main.async{
self.TableView.reloadData()
}
}
}
})
}
Didn't tested it so let me know if you still have issue with above code.
func deletePost() {
let uid = FIRAuth.auth()!.currentUser!.uid
let storage = FIRStorage.storage().reference(forURL: "gs://gsignme-14416.appspot.com")
FIRDatabase.database().reference().child("posts").child(uid).observe(.childAdded, with: { (snapshot) in
let indexPath = self.selectedIndex
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
self.key = post["postID"] as? String
self.itemsRef = FIRDatabase.database().reference().child("posts").child(uid).child(self.key!)
// Remove the post from the DB
FIRDatabase.database().reference().child("books").child(self.key!).removeValue { error in
if error != nil {
print("error \(error)")
return // use return here or use if else
}
// no error has occurred,hence move on to remove the post from the array
self.posts.remove(at: indexPath.row)
} })
DispatchQueue.main.async {
self.TableView.reloadData()
}
}
Delete the removed object from your array also. Your table view gets populated using posts array, not from Firebase object itself. when you click delete button the data is removed from Firebase but it remains on your app, as you have not deleted that object from your array i.e. posts thats why it doesn't remove the data from the tableView until you close the app and reopen it.
You need the remove the deleted object from your posts array and reload that rows to get the effect.
self.posts.remove(at: indexPath.row)
and then reload that specific row itself.
self.tableView.beginUpdates
tableView.reloadRows(at: [indexPath], with: .top)
self.tableView.endUpdates;
In your case
FIRDatabase.database().reference().child("books").child(self.key!).
removeValue { error in
if error != nil {
print("error \(error)")
} else{
self.posts.remove(at: indexPath.row)
self.tableView.beginUpdates
tableView.reloadRows(at: [indexPath], with: .top)
self.tableView.endUpdates;
}
}
})
Hope it helps. Happy Coding!!

Fetch data from Firebase Database to tableview

First I get these values into my Firebase Database, which works successfully.
func handleSale() {
let ref = FIRDatabase.database().reference().child("tickets")
let childRef = ref.childByAutoId()
guard let Price = emailTextField.text, ticketName = passwordTextField.text else {
print("Form is not valid")
return
}
let values: [String: AnyObject] = ["Price": Price, "ticketName": ticketName]
ref.observeEventType(.ChildAdded, withBlock: { (snapshot) in
let ticketId = snapshot.key
let ticksRef = FIRDatabase.database().reference().child("tickets").child(ticketId)
ticksRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
guard let dictionary = snapshot.value as? [String: AnyObject] else {
return
}
self.messages.append(Ticket(dictionary: dictionary))
childRef.updateChildValues(values) { (error, ref) in
if error != nil {
print(error)
return
}
print(snapshot)
print(dictionary)
print(ticketId)
}
}, withCancelBlock: nil)
})
}
I then tried to setup a tableview with a custom cell class and fetch these values into the tableview, however being fairly new to swift, I know I haven't done this correctly. In the end I would like that my values "price" and "ticketName" shows up in my table's..
here is my tableview:
import UIKit
import Firebase
class Sales: UITableViewController {
let cellId = "cellId"
var messages = [Ticket]()
var messagesDictionary = [String: Ticket]()
override func viewDidLoad() {
super.viewDidLoad()
var messages = [Ticket]()
var messagesDictionary = [String: Ticket]()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Logout", style: .Plain, target: self, action: #selector(heya))
tableView.registerClass(FredericCell.self, forCellReuseIdentifier: cellId)
func fetchTicket() {
FIRDatabase.database().reference().child("tickets").observeEventType(.ChildAdded, withBlock: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let ticketId = snapshot.key
Ticket.setValuesForKeysWithDictionary(dictionary)
return
}
}, withCancelBlock: nil)
}
print("test to see if it works")
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellId, forIndexPath: indexPath) as! FredericCell
return cell
}
func heya() {
print ("working")
}
I have edited in hope of filling out my cells, but without luck..:
override func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return tickets
.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellId, forIndexPath: indexPath) as! FredericCell
let ticket = tickets[indexPath.row]
cell.textLabel?.text = ticket.ticketName
cell.detailTextLabel?.text = ticket.Price
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
return cell
}

Resources