I have a standard CoreData/NSFetchedResultsController/UITableView setup. There is no edit button on top, but right-swiping a row will reveal the delete button. If the delete button pressed, I want to pop up a UIActionSheet to confirm the deletion.
Before the UIActionSheet, I just implemented tableView:commitEditingStyle:forRowAtIndexPath: and in it, I called deleteObject: on the managedObjectContext for the indexPath and let the FetchedResultsController overrides handle the visual aspect of it.
Now, I have tableView:commitEditingStyoe:forRowAtIndexPath: create the UIActionSheet and show it. I implemented actionSheet:clickedButtonAtIndex: and put the deleteObject: in the case where the delete button is pressed. The problem is that I need the indexPath of the item to delete and it is no longer in scope.
Do I...
A) delete the ManagedObject in tableView:commitEditingStyle:forRowAtIndexPath: and either save or roll back in actionSheet:clickedButtonAtIndex: ?
B) store the index path as a property so I can set it in tableView:commitEditingStyle:forRowAtIndexPath: and read it in actionSheet:clickedButtonAtIndex:?
C) something else...
Option A seems like more overhead than necessary. Either way it will be deleted, just sometimes it will be rolled back.
Option B seems very hawkish. An external value like that doesn't seem to fit the object oriented model very well.
Is there a generic object in UIActionsSheet for passing in values like this? Or is there something else I'm missing?
I needed a similar solution. I solved this by creating an ivar NSIndexPath * currentPath; and used the following idea for tableView:commitEditingStyle:forRowAtIndexPath:
- (void) tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
UIAlertView * alert;
UITableViewCell * cell;
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// save current indexPath
[currentPath release];
currentPath = [indexPath retain];
// display warning
alert = [[UIAlertView alloc] initWithTitle:#"Really?"
message:#"Are you really really sure you want to delete this item?"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Delete", nil];
[alert show];
[alert release];
// clear delete confirmation from table view cell
cell = [tableView cellForRowAtIndexPath:indexPath];
[cell setEditing:NO animated:NO];
[cell setEditing:YES animated:YES];
};
return;
}
If the user taps the delete button, I then remove the row from the UITableViewCell and remove the entry from the data source. If they tap cancel, I simply ignore the action.
Instead of the indexPath I would store the managed object itself in a property in the -tableView:commitEditingStyle:forRowAtIndexPath: method:
self.deleteCandidate = [self.controller objectAtIndexPath:indexPath]
and then delete that object (or not) in -actionSheet:clickedButtonAtIndex:, and set self.deleteCandidate = nil in either case.
The reason is that the fetched results controller objects might change between those two methods being called.
Another option is to use "Associative References" (objc_setAssociatedObject, objc_getAssociatedObject) to store a reference from the UIActionSheet to the object in question.
Related
Using the following delegate method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
PLOTCheckinTableViewCell *cell = (PLOTCheckinTableViewCell *)[self.checkinsTableView dequeueReusableCellWithIdentifier:CheckinCellIdentifier forIndexPath:indexPath];
[cell setSwipeGestureWithView:crossView color:redColor mode:MCSwipeTableViewCellModeSwitch state:MCSwipeTableViewCellState2 completionBlock:^(MCSwipeTableViewCell *cell, MCSwipeTableViewCellState state, MCSwipeTableViewCellMode mode) {
self.indexPathToDelete = [tableView indexPathForCell:cell];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Delete?"
message:#"Are you sure your want to remove this checkin?"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
[alertView show];
}];
}
Then inside the UIAlertView delegate method:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
// No
if (buttonIndex == 0) {
}
// Yes
else {
PLOTCheckinTableViewCell *cell = (PLOTCheckinTableViewCell *)[self.checkinsTableView cellForRowAtIndexPath:self.indexPathToDelete];
[self.checkins removeObjectAtIndex:self.indexPathToDelete.row];
[self.checkinsTableView deleteRowsAtIndexPaths:#[self.indexPathToDelete] withRowAnimation:UITableViewRowAnimationFade];
self.indexPathToDelete = nil;
}
}
However, whenever I hit "Ok" in the alert view, the cell that's deleted is always the last one in the tableview, ie. not the cell the user actually swiped.
Is it something to do with the dequeuing?
Yes. You should not be referencing to your cell. Instead, try to reference to the actual object that caused the cell to exist (i.e. if this is a list of messages, the message that is displayed within that cell).
The cell object is being reused and the UIAlertView wont know about that. Even if you have 1000 items in your list, you are not going to have more than 20 cells. They are always going to be reused thorough your table view scrolling.
You should be looking into removing an element from your data source array instead of removing the cell itself. After removing the element, you can always reload your table view to visually reflect the elemnt removed state.
I have the following UITableViewCell (well, subclassed).
With didSelectRowAtIndexPath it is possible to capture that a cell has been selected in UITableViewController. My problem occurs due to the fact that directly pressing Choose User bypasses the selection of the cell.
How could I allow my UITableViewController to be aware that UITableViewCell foo has been pressed even if the user immediately hits Choose User?
N.B. I don't need the Selection capability per se, this was just by method of knowing that a user had tapped within a cell area.
You could just call the method directly. If we say that for each Choose User button we are setting the row number as the tag and assuming that you don't have sections so everything will happen in section 0 we could do.
- (void)hitChooseUser:(id)sender
{
// Do whatever you want for when a user hits the `Choose User` button
// Code......
// Then do this at the end or whenever you want to do it.
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[sender tag] inSection:0];
// Also assuming you have created you have created you UITableView correctly.
[self tableView:myTableView didSelectRowAtIndexPath:indexPath];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do whatever it is you want.
}
I also found this link that may help you Manually call didSelectRowatIndexPath
You could also disable the user interaction with the cell itself by setting userInteractionEnabled: to NO for each cell in cellForRowAtIndexPath: so didSelectRowAtIndexPath: will only get called when you want to call it manually.
Do not call didSelectRowAtIndexPath: It is a UITableViewDelegate method and, where possible, should be used as such (meaning let the UITableView send messages to it). In addition, it creates an unnecessary dependency on UITableView implementation.
That being said, in order to achieve shared behavior that is performed either on button click, or on row selection, refactor it out into a common method that is not coupled with UITableViewDelegate
For example:
-(void)doSomethingCommon {
//do shared code here
}
-(void)chooseUserButtonPressed:(id)sender {
[self doSomethingCommon];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self doSomethingCommon];
}
And if your UITableView shows more than one of these rows, for which you depend on knowing which corresponding model object is related to the cell, than you can use the tag property on UIView subclasses (usually something in your cell) to mark the row that the object is shown in.
I have a small amount of code from which I want only a certain view controller to use. The view controller shares a class with another controller.
Is it possible to target the if statement based on the push segue identifier?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIAlertView *messageAlert = [[UIAlertView alloc]
initWithTitle:#"Row Selected" message:#"Added to Routine!" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
I do have a IF statement within my prepareForSegue: method but I wish to have one within my didSelectRowAtIndexPath:
if ([segue.identifier isEqualToString:#"ShowSightingDetails"])
I have looked around but can't see anything directly associated with this.
This should be something that is fairly simple I would of thought.
Add a property, something like #property (assign, nonatomic) BOOL shouldAlert; and set its value in prepareForSeague, then you can refer to it later.
I suppose you have specified different cell identifiers for your cells in your storyboard. You could get the cell in tableView:didSelectRowAtIndexPath: and check its reuseIdentifier. The Code could be something like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([cell.reuseIdentifier isEqualToString:#"Identifier1"]) {
// Do something
} else if ([cell.reuseIdentifier isEqualToString:#"Identifier2"]) {
// Do something different
}
}
I have a UITableViewCell and configured it that editing is enabled.
Everything works fine but my problem is that the delete confirmation doesn't disappear when the round delete button (the circle with a "|") is pressed the second time.
Does anybody know why this could happen?
[EDIT]
I narrowed it down to a method which is called every second and in that method I call beginUpdates and endUpdates on the editable UITableView. Does anybody knows a workaround?
If I had to take a stab in the dark... You probably are setting editing to true and then not setting it back to false. Make sure whatever method you call to set the value of editing reverses its value each time it's called.
- (void)setTableDelete
{
bool temp = !myTableView.editing;
[myTableView setEditing:temp animated:YES];
}
Try adding something like this:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
Dashboard *dashboard = [dashboards dashboardAtIndex:indexPath.row];
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
NSLog(#"delete");
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
NSLog(#"insert");
}
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
Reloading the rows is key.
Solved it by avoiding the use of beginUpdates & endUpdates methods if tableView is in edit mode, instead i update the cells manually if edit mode is YES.
I have a second confirmation dialog that appears when a user decides to delete a UITableViewCell. Here is my table view in its normal state:
And here it is when the whole table goes into editing mode:
Now when the user taps one of the red minus signs on the left, the cell goes into delete confirmation mode:
If the user then taps the delete button that has just appeared, this action sheet appears:
This is where the problem arises. If the user confirms that they want to delete the map, all is well. But if the cancel button is pressed, the action sheet disappears, but the table view still looks like this:
The issue is that the delete confirmation button should no longer be in its selected state, and should have hidden itself from view. As you can see it hasn't. My searches in the documentation have ended with no results. I don't want to setEditing:NO because I want the table to remain in its normal editing state. Any ideas?
Edit 1: Here is what goes on inside tableView:commitEditingStyle:forRowAtIndexPath:
- (void)tableView:(UITableView*)tableView commitEditingStyle:
(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath*)indexPath {
NSInteger finalIndex = [self getFinalIndexForIndexPath:indexPath];
NSString* mapTitle = ((PreviewDataObject*)[[[SingletonDataController sharedSingleton]
getImportedOrSavedMapPreviews:masterIdentifierString]
objectAtIndex:finalIndex]).titleString;
UIActionSheetWithIndexPath* sheet = [[UIActionSheetWithIndexPath alloc] initWithTitle:
[NSString stringWithFormat:#"Are you sure you want to delete the map '%#'?",
mapTitle] delegate:self cancelButtonTitle:#"Cancel"
destructiveButtonTitle:#"Delete Map" otherButtonTitles:nil];
sheet.indexPath = indexPath;
[sheet showFromTabBar:[AppDelegate appDelegate].tabBarController.tabBar];
[sheet release];
}
After that, you may as well present an AlertView asking if they really, really, really want to delete.
The 2-step process of turning on the button and then pressing delete should be plenty of confirmation.
Doing what you're doing does not seem to me to be standard practice, and once you get into that area you're going to find holes in the framework.
I agree with the above discussion that needing to hit delete 3 times is a bad idea.
However, for other actions there may be a good reason for resetting the row. The simplest solution is just reloading that one row.
Swift 2.2:
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
Just try this and tell me its working for you or not.
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Delete the row from the data source.
[arr removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert)
{
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
Simply exit the editing mode (Swift):
tableView.setEditing(false, animated: true)