Deleting an object in a tableView cell from Parse - ios

I am attempting to delete certain objects from Parse. The user will be able to save posts and I am trying to implement a way for them to delete them. The saved posts can be found in a TableView.
I currently have the following.
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .Default, title: "Remove") { (action: UITableViewRowAction!, indexPath: NSIndexPath!) -> Void in
let query = PFQuery(className: "SavedObjects")
query.orderByDescending("createdAt")
query.whereKey("savedByUser", equalTo: (PFUser.currentUser()?.username)!)
query.findObjectsInBackgroundWithBlock({ (objects : [PFObject]?, error: NSError?) -> Void in
if error == nil {
for object in objects! {
object.deleteInBackground()
}
}
})
self.tableView.reloadData()
}
deleteAction.backgroundColor = UIColor.redColor()
return [deleteAction]
}
The object.deleteInBackground() does work, but it deletes all objects that were saved by the user. I would like it to only delete the object that the cell represents.
I have also tried to use object.deleteInBackgroundWithTarget([indexPath.row], selector: nil) but have had no luck.

if I understood your question correctly, I think you need to add another query.WhereKey() filtering by the objectId of the corresponding row saved on Parse. This way the call to object.deleteInBackground() will only delete the cell you selected from the TableView. I hope this was helpful!
Valerio

Related

Swift 3 - Core data One-to-One relation get value

I have a one-to-one relation in my core data, using UIAlertAction I am saving some data and it is successfully saved to core data with no error
Now I want to get those data and set it to a UILabel in my tableview
This is my Core Data
Using editActionsForRowAt I am saving the values of reps and sets
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editButton = UITableViewRowAction(style: .normal, title: "Edit") { (rowAction, indexpath) in
let sets = self.workout?.muscleList?[indexpath.row]
let inputAlert = UIAlertController(title: "Sets", message: "Enter reps and sets", preferredStyle: .alert)
inputAlert.addTextField { (textfield:UITextField) in
textfield.placeholder = "Sets"
}
inputAlert.addTextField { (textfield:UITextField) in
textfield.placeholder = "Reps"
}
inputAlert.addAction(UIAlertAction(title: "Save", style: .default, handler: { (action:UIAlertAction) in
let setsTextField = inputAlert.textFields?.first
let repsTextField = inputAlert.textFields?.last
if repsTextField?.text != "" && setsTextField?.text != "" {
print("results: ",(setsTextField?.text)!, " sets + ", (repsTextField?.text)!," reps")
sets?.setAndrep?.sets = setsTextField?.text
sets?.setAndrep?.reps = repsTextField?.text
do{
try sets?.managedObjectContext?.save()
print("saved the sets and reps at row: ",indexpath.row)
}catch{
print("Set and rep could not be added")
}
self.newMusclesTableView.reloadData()
}
}))
inputAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(inputAlert, animated: true, completion: nil)
}
let deleteButton = UITableViewRowAction(style: .destructive, title: "Delete") { (rowAction, indexpath) in
self.deleteMuscle(at: indexPath)
}
deleteButton.backgroundColor = UIColor.red
return [editButton,deleteButton]
}
And in my CellForRowAt
I am trying to get the saved value and set it to the UILabel however it does not seem to be getting the value at all
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = newMusclesTableView.dequeueReusableCell(withIdentifier: "muscleCell", for: indexPath) as? muscleListTableViewCell
cell?.cellView.layer.cornerRadius = (cell?.cellView.frame.height)! / 2
if let muscles = workout?.muscleList?[indexPath.row]{
cell?.muscleTitle?.text = muscles.name
cell?.myBtn.addTarget(self, action: #selector(self.btnAction(_:)), for: .touchUpInside)
cell?.setsRepsLabel?.text = muscles.setAndrep?.sets ?? "did not get value"
}
return cell!
}
The UILabel is set to "did not get value" which means it could not get muscles.setAndrep?.sets
Can someone please help me on what I am doing wrong?
A few tips before I answer the question:
try to minimise the spacing between lines so that you can see the whole method in a glance, this helps readability massively (you don't have to scroll or cast your eyes too far to see the whole method at once) (If you do later have a method that is too long to fit on a screen without any line spaces, that is usually an indicator it needs to be split into multiple methods)
It might help you to extract the code within the save action out to its own method, so that you can re-use the code somewhere else that might want to save it, plus your code is more readable and modularised this way (google single responsibility principle, its a really useful way of programming that will save you so much time debugging things later =])
e.g.:
func saveSetAndRepToMuscleList(set: String, rep: String, setrep: SetRep) {
... // code that saves a set and rep to CoreData
}
that you would call this in the action callback:
let setrep = self.workout?.muscleList?[indexpath.row]
let sets = inputAlert.textFields?.first?.text
let reps = inputAlert.textFields?.last?.text
saveSetAndRepToMuscleList(sets, reps, setrep)
To your question, do you have code that refreshes / fetches self.workout?.muscleList when changes are made?
This might be where you have an issue, since you have said the data is being saved to core data correctly but not shown in your UI. But alternatively it may be that it your variables are changing in your code but aren't actually being saved to core data. There exist some tools to read the contents of your core data db to enable you to check, like https://github.com/ChristianKienle/Core-Data-Editor
You might want to check if using the context from a model object to perform a save is the issue
try sets?.managedObjectContext?.save()
Personally I set up a single reference to the moc (Managed Object Context) and use the moc to call save etc. The way I usually obtain the moc is when the controller is created I get a reference from the app delegate, or a class I have made that manages the core data objects (a CoreDataManager of some sort).
Check out this guide (and be aware that the talk is a few years out of date, but should still be good for getting the concepts right) https://academy.realm.io/posts/jesse-squires-core-data-swift/
The code has been kept up to date https://github.com/jessesquires/JSQCoreDataKit

Removing value from UITableView causes crash

I have a UITableView and when I click on a cell I update a value in firebase to be true. I also implemented the ability to delete the UITableViewCell. The problem is that if the cell has been clicked on (updated) and then deleted the app crashes. When I have a look in firebase the value that has been updated has not been deleted (everything else has been), this only happens if the cell has been updated. This is what I got
// remove task
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
let taskToDelete = groupTask[indexPath.row]
if editingStyle == UITableViewCellEditingStyle.delete {
groupTask.remove(at: indexPath.row)
DataService.instance.REF_GROUPS.child(group!.key).child("task").child(taskToDelete.id).removeValue(completionBlock: { (error, refer) in
if error != nil {
} else {
}
})
self.tableView.reloadData()
}
}
// update task value
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? GroupTaskCell else { return }
if cell.isSelected == true {
let selected = groupTask[indexPath.row]
DataService.instance.REF_GROUPS.observe(.value) { (snapShot) in
DataService.instance.updateTaskStatus(desiredGroup: self.group!, selected: true, atIndexpath: indexPath.row, childPath: selected.id, handler: { (complete) in
self.tableView.reloadData()
})
}
} else {
}
self.tableView.reloadData()
}
// update task status
func updateTaskStatus(desiredGroup: Group, selected: Bool, atIndexpath: Int, childPath: String, handler: #escaping (_ taskArray: [Task]) -> ()) {
REF_GROUPS.child(desiredGroup.key).child("task").child(childPath).updateChildValues(["selected": selected])
}
EDIT!
Could not cast value of type 'NSNull' (0x1b5915f18) to 'NSString' (0x1b5921ad8).
2018-01-01 17:38:53.431901+0200 WireUp[587:103199] Could not cast value of type 'NSNull' (0x1b5915f18) to 'NSString' (0x1b5921ad8).
(lldb)
What firebase looks like before I delete the task:
And after:
As you can see it is not removing the value that has been updated and that is what's causing the crash. I appreciate all help.
func addTask(withTask task: String, andPeople people: String, forUID uid: String, withGroupKey groupKey: String?, selectedStatus selected: Bool, sendComplete: #escaping (_ taskCreated: Bool ) -> ()) {
if groupKey != nil {
REF_GROUPS.child(groupKey!).child("task").childByAutoId().updateChildValues(["taskToDo": task, "peopleToDoTask": people, "senderId": uid, "selected": selected])
sendComplete(true)
} else {
}
}
func updateTaskStatus(desiredGroup: Group, selected: Bool, childPath: String, handler: #escaping (_ taskArray: [Task]) -> ()) {
REF_GROUPS.child(desiredGroup.key).child("task").child(childPath).updateChildValues(["selected": selected])
}
func configureTaskCell(taskToDo task: String, nameForPerson name: String, isSelected: Bool ) {
self.task.text = task
self.name.text = name
if isSelected {
self.chechImg.isHidden = false
} else {
self.chechImg.isHidden = true
}
}
Instead of using self.tableView.reloadData() from within the table view delegate functions try updating and deleting cells using the beginUpdate() and endUpdate() methods of UITableView. You won't crash and you won't be reloading the whole table. In Obj-C, it looks like this for removing and updating a cell:
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
[tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects: indexPath, nil] withRowAnimation:UITableViewRowAnimationNone];
[tableView endUpdates];
It seems as though the code you've provided isn't the issue. It's being deleted correctly but as you delete it, it's also updating again with just the selected: true. Somewhere in your code, it'll be trying to fetch the rest of the data, including the selected: true BUT it's not there; resulting in the crash. You may want to do some debugging to see where you're actually updating this selected value to your database and get rid of it

How to reload tableview after adding new entry?

I am creating a cloudkit tableview. I load the app and my tableview appears with my entries from cloud kit.
I then use my add method insertNewObject which adds the record to cloud kit but this does not show up in my tableview. It will only show up on my next run of the app.
func insertNewObject(sender: AnyObject) {
let record = CKRecord(recordType: "CloudNote")
record.setObject("New Note", forKey: "Notes")
MyClipManager.SaveMethod(Database!, myRecord:record)
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
This is my add method. I am calling tableview reload as you can see but nothing is happening.
My tableview creation code:
// Tableview stuff --- Done
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
/////// Get number of rows
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
//// FIll the table
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let object = objects[indexPath.row]
cell.textLabel!.text = object.objectForKey("Notes") as? String
return cell
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
As requested: Method that saves to CloudDB
func SaveMethod(publicDatabase: CKDatabase, myRecord: CKRecord ) -> CKRecord {
publicDatabase.saveRecord(myRecord, completionHandler:
({returnRecord, error in
if let err = error {
self.notifyUser("Save Error", message:
err.localizedDescription)
} else {
dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("Success",
message: "Record saved successfully")
}
}
}))
return myRecord
}
My viewdidload method in masterview:
override func viewDidLoad() {
super.viewDidLoad()
// Database loading on runtime
Database = container.privateCloudDatabase
///Build Query
let query = CKQuery(recordType: "CloudNote", predicate: NSPredicate(format: "TRUEPREDICATE"))
///Perform query on DB
Database!.performQuery(query, inZoneWithID: nil) { (records, error) -> Void in
if (error != nil) {
NSLog("Error performing query. \(error.debugDescription)")
return
}
self.objects = records!
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
You should not reload your entire tableView when you insert a single object. Only do that when you know ALL the data has changed.
To do what you want, this is the order:
Insert a new data object into your datasource (self.objects). Make sure you get the index of where it ends up in the array.
Call insertRowAtIndexPath: with the correct indexPath on your tableView. This will make sure your data and tableView are in sync again, and tableView:cellForRowAtIndexPath: is called for at least your new data object (and possible others, as certain cells might now be reused to display other data).
Note that the order is always: update your data first, then update your UI (the only place I know of that his is hairy is when using a UISwitch).

Swift 2 + Parse: Array index out of range

SOMETIMES THE REFRESH WORKS SOMETIMES IT DOESN'T
I have a UITableViewController which is basically a news feed. I have also implemented a pull to refresh feature. However sometimes when I pull to refresh it gives me the error
'Array index out of range'.
I know this means an item it is trying to get does not exist but can you tell me why? Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
refresher = UIRefreshControl()
refresher.attributedTitle = NSAttributedString(string: "Pull to refresh")
refresher.addTarget(self, action: "refresh", forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(refresher)
refresh()
tableView.delegate = self
tableView.dataSource = self
}
and the refresh() function:
func refresh() {
//disable app while it does stuff
UIApplication.sharedApplication().beginIgnoringInteractionEvents()
//get username and match with userId
let getUser = PFUser.query()
getUser?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if let users = objects {
//clean arrays and dictionaries so we dont get indexing error???
self.messages.removeAll(keepCapacity: true)
self.users.removeAll(keepCapacity: true)
self.usernames.removeAll(keepCapacity: true)
for object in users {
if let user = object as? PFUser {
//make userId = username
self.users[user.objectId!] = user.username!
}
}
}
})
let getPost = PFQuery(className: "Posts")
getPost.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if error == nil {
if let objects = objects {
self.messages.removeAll(keepCapacity: true)
self.usernames.removeAll(keepCapacity: true)
for object in objects {
self.messages.append(object["message"] as! String)
self.usernames.append(self.users[object["userId"] as! String]!)
self.tableView.reloadData()
}
}
}
}
self.refresher.endRefreshing()
UIApplication.sharedApplication().endIgnoringInteractionEvents()
}
and:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let myCell = tableView.dequeueReusableCellWithIdentifier("SinglePostCell", forIndexPath: indexPath) as! PostCell
//ERROR GETS REPORTED ON THE LINE BELOW
myCell.usernamePosted.text = usernames[indexPath.row]
myCell.messagePosted.text = messages[indexPath.row]
return myCell
}
You have a race condition given you are doing two background tasks, where the second depends on values returned from the first. getUser?.findObjectsInBackgroundWithBlockwill return immediately, and getPost.findObjectsInBackgroundWithBlock will start executing. The getPost should be inside the block for getUser, to ensure the sequence is correct.
Similarly, the following two lines should be inside the second block:
self.refresher.endRefreshing()
UIApplication.sharedApplication().endIgnoringInteractionEvents()
Given the error line, you probably also have a race condition between the two background tasks and displaying the tableView. I would be inclined to try:
func tableView(tableView:UITableView!, numberOfRowsInSection section:Int) {
return self.refresher.refreshing ? 0 : self.usernames.count
}
This way you won't touch self.usernames until the background refresh is finished (as long as you remember to put endRefreshing inside the second block, which is also put inside the first block).
I Believe that in self.users[user.objectId!] = user.username! the user.ObjectId is some random value assigned by parse which looks like this: "34xcf4". This is why you might be getting 'Array index out of range'.
There are two required methods for configuring a UITableView:
tableView(_:cellForRowAtIndexPath:)
and
tableView(_:numberOfRowsInSection:)
In your code you are presenting only one required method, if you don't implement the second method then it that may cause errors.
Check the documentation at:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewDataSource_Protocol/#//apple_ref/occ/intfm/UITableViewDataSource/tableView:cellForRowAtIndexPath:
You are calling self.tableView.reloadData() on every addition to your array and doing so in a background thread.
As a general rule, you should not do UI updates in a background thread. When you clear self.messages and self.usernames, because you are in background thread, nothing prevents the tableview from trying to get a cell at an index that no longer has any data in the array.
If you want to keep your code in the background thread (risky as it may be), you should at least call .beginUpdates before reloading your arrays and wait until they're all done before calling reload and endUpdates.

PFQuery inside cellForRowAtIndexPath run multiple times

I'm using Parse and I'm building social networking app, user can comment on a post and another users could like that post just like Facebook App.
This is a piece of my code
override func viewDidLoad() {
super.viewDidLoad()
// Get all comments
var query = PFQuery(className: "Comment")
query.findObjectsInBackgroundWithBlock { comments, error in
if error == nil {
self.comments = comments
}
}
}
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let commentForThisRow = self.comments[indexPath.row] // contains array of PFObject
// Get all activities which like this comment
var likeQuery = PFQuery(className: "Activity")
likeQuery.whereKey(kSFActivityType, equalTo: kSFActivityTypeLike)
likeQuery.whereKey(kFSActivityComment, equalTo: commentForThisRow)
likeQuery.findObjectsInBackground {
// Store array of users who like this comment to the cell
}
}
The problem is, because cell for row at indexpath called everytime user scrolls the tableView, it makes the likeQuery run everytime and do request.
How to make the likeQuery request only run one time?

Resources