Firebase: delete item from row (Swift 3) - uitableview

I have a tableview showing data from a Firebase console (JASON structure) like this:
chklst-xxx
- avaliation
-students
-KHZAjBi44eZ8JsaswkKI
-teacher: Paul
-datTime: 09.12.2016 12:25pm
-name: Mary Anne
-mark: 7.5
-preceptor: John
-KHWZlh7aasy78ahsiuKIi0
-teacher: Paul
-datTime: 09.12.2016 12:48pm
-name: Anne Caroline
-mark: 9.4
-preceptor: Peter
These data are shown at a tableview, each one, after node students, except the key. I have one function to delete an especific row, as i do with other apps in CoreData (tableview editingStyle):
// FUNC DELETAR
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
teste.remove(at: indexPath.row)
ref.removeValue() // HERE IS MY DOUBT
tableView.reloadData()
}
}
I can erase the item from the array and from the table, but i just don't know how to do it from Firebase. I have tried other ways with removeValue, but it just didn't work, except an way to erase the whole data, not specifically that row selected.
let ref = FIRDatabase.database().reference().child("avaliation").child("students")
I need some teaching to make this works. Thank you.
Here is my method for Saving this data: (Added 09.12 19:30pm)
func Save(){
let ref = FIRDatabase.database().reference().child("avaliation").child("students").childByAutoId()
referencia.child(“name”).setValue(Name)
referencia.child(“teacher”).setValue(Teacher)
referencia.child("preceptor").setValue(Preceptor)
referencia.child("datTime”).setValue(DateTime)
referencia.child(“mark”).setValue(ResultComFReFC)
}
And here is my Class where I work with the main Array:
import Foundation
import Firebase
struct ChkList {
var name: String?
var teacher: String?
var preceptor: String?
var DateToday: String?
var mark: String?
var key: String!
init(name: String, teacher: String, preceptor: String, mark: String, DateToday: String, key: String = "") {
self.name = name
self.teacher = teacher
self.preceptor = preceptor
self.mark = mark
self.DateToday = DateToday
self.key = key
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
name = snapshotValue["name"] as? String
teacher = snapshotValue["teacher"] as? String
preceptor = snapshotValue["preceptor"] as? String
mark = snapshotValue["mark"] as? String
DateToday = snapshotValue["DateToday"] as? String
}
func toAnyObject() -> Any {
return [
"name": name,
"teacher": teacher,
"preceptor": preceptor,
"mark": mark,
"DateToday": DateToday ]}}
UPDATE: Tableview EdityingStyle
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let ref = FIRDatabase.database().reference().child("avaliation").child("students").childByAutoId()
teste.remove(at: indexPath.row) //Array delet item
let userKeyA = ref.key
ref.child(userKeyA).removeValue()
tableView.reloadData()
}
}

My suggestion, store the pushKey of the user by using 'getKey' method when you push the user details.
chklst-xxx
- avaliation
-students
-KHZAjBi44eZ8JsaswkKI
-teacher: Paul
-userKey:KHZAjBi44eZ8JsaswkKI <--------------
-datTime: 09.12.2016 12:25pm
-name: Mary Anne
-mark: 7.5
-preceptor: John
Now you can retrieve the 'key` of the user. Suppose you are listing, users in the list view and would want to delete a row.
let ref = FIRDatabase.database().reference().child("avaliation").child("students").child(students.getUserKey()).removeValue();
Let me know, if it works.
Update: I don't know swift. But i'll give u an abstract idea.
When you create a new student, you refer student node and push() right. Like below,
DatabaseReference studentsRef = FIRDatabase.database().reference().child("avaliation").child("students").push();
And then you put your data like,
studentRef.child("teacher").setValue("Paul");
studentRef.child("userKey").setValue(studentRef.getKey()); <------------

I did it, using suggests above and studying and trying a lot.
The way to the method works is really capture the userkey.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let user: ChkList = teste[indexPath.row]
let uk = user.key
print("Userkey: \(uk)")
ref.child(uk!).removeValue()
}
}
This way you will have removed the data from Firebase and the row form tableview.

This answer moves the Firebase delete behavior behind the scenes so as
to not interfere with the normal tableview loading mechanisms. One
benefit is reducing the number of calls to Firebase and removing
timing issues from the internal tableview animation process.
What worked for me is getting the Firebase stuff out of the way. In my case, I collect all the keys that I want. During the delete, I remove the values from my local object, and when leaving the scene I call my cleanup method from viewDidDisappear() to sync the updated data back to Firebase:
func syncFavorites() {
//ref is the leaf for the favorites
//clear existing
ref?.removeValue()
//temp collection
var faves = [String:Any]()
//dataArrayKeys is my collection of keys on self
dataArrayKeys.keys.forEach { (key) in
faves.updateValue("true", forKey: key)
}
ref?.updateChildValues(faves)
}
I handle my tableview's delete action using the normal method.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
if let key = dataArr[indexPath.row]["key"] as? String {
dataArrayKeys.removeValue(forKey: keyToDelete)
dataArr.remove(at: indexPath.row)
}
tableView.deleteRows(at: [indexPath], with: .fade)
self.tableView.reloadData()
}
}
I have no doubt there is probably a better way. This was the simplest possible thing that worked for me.

Related

tableview commit editingStyle is removing the wrong entry

The following code is deleting the wrong entry. I have an array that shows a list of events. in the debugger indexPath.row shows 1 (which is the entry I selected to delete. However when the view refreshes it has deleted the entry 4.
func tableView(_ tableView: UITableView,
commit editingStyle: UITableViewCellEditingStyle,
forRowAt indexPath: IndexPath) {
print(type(of: selectedRecipient))
var eventsOnArray = selectedRecipient?.events?.allObjects
guard let event = eventsOnArray?[indexPath.row] as? Event, editingStyle == .delete else {
return
}
managedContext.delete(event)
do {
try managedContext.save()
print("Deleted")
eventsOnArray?.remove(at: indexPath.row)
getEvents()
self.eventList.reloadData()
} catch let error as NSError {
print("Saving error: \(error), description: \(error.userInfo)")
}
}
My guess here is that the order of your data source is not the same as the array you use in the code we see.
I base this on the fact that you call events?.allObjects which suggests that events is a NSSet which is unordered and calling allObjects on it gives you an array with an undefined order. You need to have an array instead of a set so you can guarantee the same sort order of your objects through different parts of your code.
Adding to #Joakim Danielson’s answer, you would add an extension for your entity to provide for an array instead of a set, like this:
extension Recipient {
var eventsArray: [Event] {
get {
if let eventSet = events as? Set<Event> {
return eventSet.sorted { (item0, item1) -> Bool in
return item0.title < item1.title
}
}
return []
}
}
}
This assumes there is a title attribute in your Event entity so the array returns all events sorted by title. Modify the sorted closure according to your needs.

Delete NSUserDefaults to corresponding UITableViewCell

I am trying to reinitialize all the data associated with a given cell when this one is deleted.
This is my function deleting the cell (cryptosArray is the array creating the cells):
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
cryptosArray.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
let userDefaults = UserDefaults.standard
let encodedData : Data = NSKeyedArchiver.archivedData(withRootObject: cryptosArray)
userDefaults.set(encodedData, forKey: "cryptosArray")
userDefaults.synchronize()
}
}
And this is a function that saves the amount entered in the textfield present in the cell:
func cellAmountEntered(_ walletTableViewCell: WalletTableViewCell, cryptoPrice: String) {
if walletTableViewCell.amountTextField.text == "" {
return
}
let str = walletTableViewCell.amountTextField.text
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US")
let dNumber = formatter.number(from: str!)
let nDouble = dNumber!
let eNumber = Double(truncating: nDouble)
walletTableViewCell.amountLabel.text = String(format:"%.8f", eNumber)
UserDefaults.standard.set(walletTableViewCell.amountLabel.text, forKey: "\(cryptoPrice)Amount")
walletTableViewCell.amountTextField.text = ""
}
As you can see I am saving walletTableViewCell.amountLabel.text with "\(cryptoPrice)Amount" inside the UserDefaults file where \(cryptoPrice) is a parameter corresponding to the cell.
How can I delete the corresponding "\(cryptoPrice)Amount" key when I delete the cell? I can't figure out how to pass the parameter to commit editingStyle: ..
The basic solution is: create an array of cryptoPrices. Each cell should have a value in there. For example, you can initialize an array with all zeros (if it works for you I don't know). Then if user enters the amount, you delete the zero from the array and .insert a new value there. So then when you do deletion you may easily find your key by indexPath.row.
Hope it helps! But note: this is a very straightforward solution.
I would do this: don't use UserDefaults for such things. It's not a DataBase. Better to use Realm or CoreData. I understand that it's harder but there are many tutorials that may help you with that. Good luck! Hope it helps.

Delete Post from Firebase Database

Post Structure
retrieving the data
I'm trying to delete posts from the Firebase Database. I'm having a ruff time removing values that are generated from an AutoID.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete{
let user = FIRAuth.auth()?.currentUser
let uid = user?.uid
ref?.child("tests").child(uid!)/* The Posts AutoID*/.removeValue()
tableView.reloadData()
}
}
You'll need to retrieve the post IDs when the data is being fetched. For example, when fetching data just grab the post ID as well.
ref.child("tests").child(uid!).observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let childSnap = child as! FIRDataSnapshot
print(childSnap.key) // SAVE THIS KEY SOMEWHERE, USE IT TO DELETE
}
)}

CoreData vs UserDefaults for saving user deletions

I'm just starting out and I've used UserDefaults once or twice, never used CoreData, so I'm not sure which to use in this situation. I have a table view, which has cells populated from an empty array, which is is populated by an array of strings, depending on the page title.
In other words, I have a function in viewDidLoad which checks the navigationItem.title, and depending on what it is, populates the empty array with the correct strings. Everything loads well but I need deleted items to remain deleted. Here is my code:
Initializations:
static var places = [String]()
var nycPlaces = ["Central Park", "Rockefeller Center", "Empire State Building"]
var londonPlaces = ["London Eye", "Windsor Castle", "Tower of London"]
var parisPlaces = ["Champs Elysee", "Eiffel Tower", "Catacombs"]
This is the function that populates the table view with the correct array - this is called in viewDidLoad:
func populateCells() {
switch navigationItem.title {
case "New York":
ListController.places = nycPlaces
case "London":
ListController.places = londonPlaces
default:
ListController.places = parisPlaces
}
tableView.reloadData()
}
And to delete, I have:
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
{
if editingStyle == .delete
{
tableView.beginUpdates()
ListController.places.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.endUpdates()
}
}
The problem is that when I navigate away from the page then re-load it, the empty array is re-popoulated with the 3 strings, regardless of which ones had been deleted before. How do I ensure that the deleted cell doesn't come back?
Thanks for any help!
You should use UserDefaults if your app is a simple as the code you posted (i.e. you're not fetching the data from some remote server and it really is just a few hard-coded values).
CoreData has a steep learning curve, it is difficult to use, and difficult to use correctly. And it is overkill for the scenario you have presented.
Also note: if you don't need this to persist across your process exiting and restarting you can just create mutable arrays that live somewhere accessible by all your classes (e.g. your AppDelegate, a global singleton class, whatever) and just modify them in place.
Since this is mostly static data, I would advise against having the overhead of CoreData. Instead I would use this approach:
var nycPlaces: [String] {
get {
guard let retval = UserDefaults.standard.object(forKey: "nyc") as? [String] else {
return ["Central Park", "Rockefeller Center", "Empire State Building"]
}
return retval
} set {
UserDefaults.standard.set(newValue, forKey: "nyc")
UserDefaults.standard.synchronize()
}
}
var londonPlaces: [String] {
get {
guard let retval = UserDefaults.standard.object(forKey: "london") as? [String] else {
return ["London Eye", "Windsor Castle", "Tower of London"]
}
return retval
} set {
UserDefaults.standard.set(newValue, forKey: "london")
UserDefaults.standard.synchronize()
}
}
var parisPlaces: [String] {
get {
guard let retval = UserDefaults.standard.object(forKey: "paris") as? [String] else {
return ["Champs Elysee", "Eiffel Tower", "Catacombs"]
}
return retval
} set {
UserDefaults.standard.set(newValue, forKey: "paris")
UserDefaults.standard.synchronize()
}
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
{
if editingStyle == .delete
{
tableView.beginUpdates()
ListController.places.remove(at: indexPath.row)
switch navigationItem.title {
case "New York":
nycPlaces = ListController.places
case "London":
londonPlaces = ListController.places
default:
parisPlaces = ListController.places
}
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.endUpdates()
}
}
P.S. - I'd also advise to have this data at a more global scope and to use an enum instead of doing string comparison against your navigation item's title :)

How can I delete a specific child node in Firebase from UITableViewCell using Swift

I have a UITableView which looks like this image
.
When I swipe to delete the record, I can remove it perfectly okay from the array in which it is stored, but I am having difficulties in accessing it in Firebase to delete it there.
My Firebase database structure is as follows for the above screenshot:
-KWc7RTuOe5PefiMM2tL
bodyPart: "Arms"
exerciseName: "Test 1 "
userId: "8rHmyTxdocTEvk1ERiiavjMUYyD3"
-KWcEbpw_f6kxcePY5cO
bodyPart: "Chest"
exerciseName: "Test 2 "
userId: "8rHmyTxdocTEvk1ERiiavjMUYyD3"
-KWcEdUN49QaJIVf0kwO
bodyPart: "Legs"
exerciseName: "Test 3 "
userId: "8rHmyTxdocTEvk1ERiiavjMUYyD3"
-KWcFrMSaLKQRxghGHyT
bodyPart: "Arms"
exerciseName: "Test 4"
userId: "8rHmyTxdocTEvk1ERiiavjMUYyD3"
How can I access the autoId value which is set when it is created e.g "-KWc7RTuOe5PefiMM2tL" so I can remove that child node?
Or alternatively could I access the exerciseName value depending on the UserId that is logged in?
To solve this issue I tried a number of different methods before finally reaching my intended result.
To delete the value, I created a reference to the child node 'userExercises', then ordered it by 'exerciseName' and then .queryEqual(toValue:) the exercise name value which I extracted form the UITableViewCell.
I then removed the snapshot value of this and the example code is below:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
if let exerciseName = exercises[indexPath.row].exerciseName {
let ref = FIRDatabase.database().reference().child("userExercises")
ref.queryOrdered(byChild: "exerciseName").queryEqual(toValue: exerciseName).observe(.childAdded, with: { (snapshot) in
snapshot.ref.removeValue(completionBlock: { (error, reference) in
if error != nil {
print("There has been an error:\(error)")
}
})
})
}
exercises.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .left)
}
}
Following on from what MHDev has already answered:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
if let exerciseName = exercises[indexPath.row].exerciseName {
let ref = FIRDatabase.database().reference().child("userExercises")
ref.queryOrdered(byChild: "exerciseName").queryEqual(toValue: exerciseName).observe(.childAdded, with: { (snapshot) in
snapshot.ref.removeValue(completionBlock: { (error, reference) in
if error != nil {
print("There has been an error:\(error)")
}
})
})
}
exercises.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .left)
}
}
It's a fairly straightforward process:
In general, a datasource for tableViews is an array. That array is built from dictionaries read from Firebase snapshots - or an array of objects built from the snapshots (recommended).
So here's an example that matches your Firebase structure (this was populated from a single node from a snapshot)
class Exercise {
key: "KWc7RTuOe5PefiMM2tL"
bodyPart: "Legs"
exerciseName: "Test 3 "
userId: "8rHmyTxdocTEvk1ERiiavjMUYyD3"
}
Then, when the user swipes row 3 for example, retrieve the Exercise object from the array, row3.
let theObject = ExerciseArray[3]
let parentNode = theObject.key
let ref = rootNode.child(parentNode)
ref.setValue(nil)
and you're done.

Resources