I want something similar as the Alarm app, where you can't swipe delete the row, but you can still delete the row in Edit mode.
When commented out tableView:commitEditingStyle:forRowAtIndexPath:, I disabled the swipe to delete and still had Delete button in Edit mode, but what happens when I press the Delete button. What gets called?
Ok, it turns out to be quite easy. This is what I did to solve this:
Objective-C
- (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Detemine if it's in editing mode
if (self.tableView.editing)
{
return UITableViewCellEditingStyleDelete;
}
return UITableViewCellEditingStyleNone;
}
Swift 2
override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
if tableView.editing {
return .Delete
}
return .None
}
Swift 3
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
if tableView.isEditing {
return .delete
}
return .none
}
You still need to implement tableView:commitEditingStyle:forRowAtIndexPath: to commit the deletion.
Just to make things clear, swipe-to-delete will not be enabled unless tableView:commitEditingStyle:forRowAtIndexPath: is implemented.
While I was in development, I didn't implement it, and therefore swipe-to-delete wasn't enabled. Of course, in a finished app, it would always be implemented, because otherwise there would be no editing.
You need to implement the CanEditRowAt function.
You can return .delete in the EditingStyleForRowAt function so you can still delete in editing mode.
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
if tableView.isEditing {
return true
}
return false
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
Swift Version:
override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
if(do something){
return UITableViewCellEditingStyle.Delete or UITableViewCellEditingStyle.Insert
}
return UITableViewCellEditingStyle.None
}
Basically, you enable or disable editing using the methods
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
If editing is enabled, the red deletion icon appears, and a delete conformation requested to the user. If the user confirms, the delegate method
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
is notified of the delete request. If you implement this method, then swipe to delete is automatically made active. If you do not implement this method, then swipe to delete is not active, however you are not able to actually delete the row. Therefore, to the best of my knowledge, you can not achieve what you asked for, unless using some undocumented, private APIs. Probably this is how the Apple application is implemented.
On C#:
I had the same issue where it was needed to enable/disable rows with Delete option on swipe. Multiple rows needed to be swiped left and get deleted, keep them in another colour. I achieved using this logic.
[Export("tableView:canEditRowAtIndexPath:")]
public bool CanEditRow(UITableView tableView, NSIndexPath indexPath)
{
if (deletedIndexes.Contains(indexPath.Row)){
return false;
}
else{
return true;
}
}
Note that deletedIndexes are a list of indexes which are deleted from the table without duplicates. This code check whether a row is deleted, then it disables swipe or vice versa.
The equivalent delegate function is Swift is canEditRowAtIndexPath.
I came across this problem either and fixed with codes below. hope it will help you.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
BOOL deleteBySwipe = NO;
for (UIGestureRecognizer* g in tableView.gestureRecognizers) {
if (g.state == UIGestureRecognizerStateEnded) {
deleteBySwipe = YES;
break;
}
}
if (deleteBySwipe) {
//this gesture may cause delete unintendedly
return;
}
//do delete
}
}
Related
UITableViewDelegate.h
// Swipe actions
// These methods supersede -editActionsForRowAtIndexPath: if implemented
// return nil to get the default swipe actions
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos);
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos);
However, I am returning nil in my trailingActions method and I still can do a full swipe to delete in my tableview. How can I prevent the full swipe? (I want the user to have to swipe then press the "Delete" button.
#available(iOS 11.0, *)
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
return nil
}
EDIT: I had implemented canEditRowAt and commit Editing style before the iOS 11/XCode 9/Swift 4 update. The full swipe was enabled even BEFORE I implemented the trailingSwipeActionsConfigurationForRowAt.
Implement like below :
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let delete = UIContextualAction(style: .destructive, title: "Delete") { (action, sourceView, completionHandler) in
print("index path of delete: \(indexPath)")
completionHandler(true)
}
let swipeAction = UISwipeActionsConfiguration(actions: [delete])
swipeAction.performsFirstActionWithFullSwipe = false // This is the line which disables full swipe
return swipeAction
}
This is the line which disables full swipe
swipeAction.performsFirstActionWithFullSwipe = false
And remove the other functions if you implement any like editingStyle and editActionsForRowAt.
I was able to disable swipe actions for particular cells by following this answer:
https://stackoverflow.com/a/50597672/1072262
Instead of returning nil you can return:
return UISwipeActionsConfiguration.init()
I have a tableView in which there is a section that is editable.
If I enable editing on the entire tableView, other cells while in editing mode are not selectable, so I need to enable the editing mode only on a specific section so that while other cells are selectable, the section is editable.
The reason that I need to set editing is those red square minus buttons that appear next to deletable cells.
Summary:
I need those red minus buttons next to cells, so I need to set editing as true, but if I do so, other cells won't be selectable thus I need to either set editing as true for a specific section, or add those red minus buttons without the editing mode.
You can implement canEditRowAtIndexPath method something like,
Obj-C
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 1) {
return YES;
}
return NO;
}
Swift :
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
if indexPath.section == 1 {
return true
}
return false
}
To enable selection during editing you need to set,
Obj-C
self.yourTableView.allowsSelectionDuringEditing = YES;
Swift
self.yourTableView.allowsSelectionDuringEditing = true
Swift 4/5
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
if indexPath.section == 1 {
return true
}
return false
}
more nice to have like this
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return indexPath.section == 0 ? true : false
}
I have a UITableView for which I have added some custom slide out buttons. These all work as expected. There is however a scenario in my table where a row can be in a state where none of the slide out buttons are relevant and so I have been returning an empty array of actions so the slide out buttons wont appear. Unfortunately once this occurs the UITableView stops calling my editActionsForRowAtIndexPath effectively disabling slide out buttons for all rows in my table ... and it seems permanent until the app is restarted.
Is this expected behaviour?
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]?
{
if mydata[indexPath.row].EditAvailable()
{
var editAction = UITableViewRowAction(style: .Default, title: "Edit", handler: editHandler)
return [editAction]
}
else
{
return []
}
}
The way I solved this problem was to implement the function,
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool
Here, you should just return false if you don't want the expected row to have the swipe feature.
So you code would look something like this
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return mydata[indexPath.row].EditAvailable()
}
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]?
{
var editAction = UITableViewRowAction(style: .Default, title: "Edit", handler: editHandler)
return [editAction]
}
The editActionsForRowAtIndexPath is then only called for the ones that you indicated are editable.
Please try to use tableView:editingStyleForRowAtIndexPath:.
You can control the on/off of the slide itself.
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
if (/* mydata[indexPath.row].EditAvailable() */) {
return UITableViewCellEditingStyleDelete;
} else {
return UITableViewCellEditingStyleNone;
}
}
(I tested in objective-c)
Avoid passing empty Arrays at -
tableView editActionsForRowAtIndexPath
Better do the condition check in -
tableView canEditRowAtIndexPath
Format your code like this :
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
if mydata[indexPath.row].EditAvailable() {
return true
}
return false
}
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]?
{
var editAction = UITableViewRowAction(style: .Default, title: "Edit", handler: editHandler)
return [editAction]
}
First, editActionsForRowAtIndexPath should return nil and not [] when then are no actions.
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]?
{
if mydata[indexPath.row].EditAvailable()
{
var editAction = UITableViewRowAction(style: .Default, title: "Edit", handler: editHandler)
return [editAction]
}
else
{
return nil
}
}
Second, you must implement editingStyleForRowAtIndexPath to disable the default "Delete" action for the other rows.
func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
if mydata[indexPath.row].EditAvailable() {
return UITableViewCellEditingStyle.Delete
}
return UITableViewCellEditingStyle.None
}
Thanks luciano for the tip empty array/nil tip
In iOS9.3, "canEditRowAtIndexPath" doesn't work!!!
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath;
So, the following code works for me. Only two methods should be implemented.
-(NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
if(//not editable){
return nil;
}
return #[//your actions];
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
if(//not editable){
return UITableViewCellEditingStyleNone;
}
return UITableViewCellEditingStyleDelete;
}
You should not return an empty array for the actions at editActionsForRowAtIndexPath. If you want to make the row not editable...
return false at canEditRowAtIndexPath. Once you return an empty array, all rows stop showing the swipe buttons. Its a strange behavior, i don't know whether its a glitch or as intended.
I implemented similar selective usage of actions for some tableCell using ob-c(have not tested in swift) a while ago but put the selectiveness in handleSwipe, below i have an action for all except the last one:
- (void)handleSwipeLeft:(UISwipeGestureRecognizer *)gestureRecognizer{
CGPoint location = [gestureRecognizer locationInView:self.tableView];
NSIndexPath *swipedIndexPath = [self.tableView indexPathForRowAtPoint:location];
if(!(swipedIndexPath.row<[coreDataHelper getAllTimerLists].count) ){
[self.tableView setEditing:!self.tableView.editing animated:YES];
}
}
Does this help you or do you need multiple different actions array, as opposed to this which only has one array and inactivates/activates it?
I want for don't editable rows change background color and label for swipe to delete button.
How to do it?
I don't want use func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool, for disable swipe to delete, because it less clear for user in my app.
Write the below tableview method
- (UITableViewCellEditingStyle)tableView:(UITableView *)aTableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
// Detemine if it's in editing mode
if (self.tableView.editing) {
return UITableViewCellEditingStyleDelete;
}
return UITableViewCellEditingStyleNone;
}
I am trying to delete some rows in my UITableView by setting allowsMultipleSelectionDuringEditing to YES. This is all working well; the circle is showing on the left hand side.
However, for certain cells, I don't want the circle on the left hand side to come up. How do I do that? I've tried cell.selectionStyle = UITableViewCellSelectionStyleNone during editing and that didn't work.
Any hints?
In order to disallow some rows from multiple selection you should use tableView:shouldIndentWhileEditingRowAtIndexPath: mixed with cell.selectionStyle = UITableViewCellSelectionStyleNone.
Here is an example from my code:
- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath*)indexPath {
if (indexPath.row < 4) {
return YES;
} else {
return NO;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// (...) configure cell
if (indexPath.row < 4) {
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
} else {
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
}
First, use these settings:
self.tableView.allowsMultipleSelectionDuringEditing = true
self.tableView.setEditing(true, animated: false)
And implement next delegate methods:
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return self.shouldAllowSelectionAt(indexPath)
}
func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return self.shouldAllowSelectionAt(indexPath)
}
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if self.shouldAllowSelectionAt(indexPath) {
return indexPath
}
return nil
}
shouldAllowSelectionAt is my private method which contains logic about which row to select
Have you tried to implement tableView:editingStyleForRowAtIndexPath: UITableViewDelegate's method and return UITableViewCellEditingStyleNone for cells you don't want to show delete control?