UITableViewCell,在滑动时显示删除按钮

581

当我在UITableViewCell上滑动时,如何让删除按钮显示出来?事件从未触发,删除按钮从未出现。


请参考我的 Swift 4 回答类似问题的解决方案,其中展示了最多3种为 UITableViewCell 创建滑动删除操作的不同方式。链接 - Imanou Petit
我8年前问过这个问题...请删除这个问题,它已经过时了。当时Swift甚至还不存在! - TheLearner
我们能否固定侧滑按钮的高度?例如:我的单元格高度为150,我只想显示50.0f的按钮。这可行吗? - Suhas Arvind Patil
这在行级别上很有效,但是有关如何将其整合到节段中的线索吗? - Frostmourne
19个回答

1050

在启动时的(-viewDidLoad或者在Storyboard中):执行以下操作:

self.tableView.allowsMultipleSelectionDuringEditing = false

为了支持表视图的条件编辑,请重写此方法。只有当你打算返回NO时,才需要实现这个方法。默认情况下,所有项目都是可编辑的。

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    // Return YES if you want the specified item to be editable.
    return YES;
}

// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        //add code here for when you hit delete
    }    
}

94
这个方法是有效的,但是...只有在你想要返回NO的情况下才需要实现-(BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath这个方法。默认情况下,所有的项目都是可编辑的,所以如果你总是返回YES,就不需要实现它。 - Thanos Diacakis
24
重要的是要知道:这些是UITableViewDataSource方法,不是UITableViewDelegate方法。 - Dave Albert
8
想知道如何实现删除 - ma11hew28
12
为了明确起见,您必须覆盖 tableView:commitEditingStyle:forRowAtIndexPath: 方法,否则侧滑手势将无法识别,并且在尝试删除时不会发生任何事情。 - Chris
好的,我将所有代码复制到我的应用程序中,但没有显示“删除”按钮。我想知道答案中的代码是否足以显示删除,还是我需要编写更多的代码? - Tony Chen
显示剩余4条评论

125

此答案已更新至Swift 3

我认为,在学习新任务时,拥有一个非常简单、自包含的示例是很好的,因为没有任何假设。这个答案就是删除UITableView行的示例。项目执行如下:

enter image description here

本项目基于Swift的UITableView示例

添加代码

创建一个新项目并使用以下代码替换ViewController.swift的代码。

import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    // These strings will be the data for the table view cells
    var animals: [String] = ["Horse", "Cow", "Camel", "Pig", "Sheep", "Goat"]

    let cellReuseIdentifier = "cell"

    @IBOutlet var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // It is possible to do the following three things in the Interface Builder
        // rather than in code if you prefer.
        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
        tableView.delegate = self
        tableView.dataSource = self
    }

    // number of rows in table view
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.animals.count
    }

    // create a cell for each table view row
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!

        cell.textLabel?.text = self.animals[indexPath.row]

        return cell
    }

    // method to run when table view cell is tapped
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("You tapped cell number \(indexPath.row).")
    }

    // this method handles row deletion
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {

        if editingStyle == .delete {

            // remove the item from the data model
            animals.remove(at: indexPath.row)

            // delete the table view row
            tableView.deleteRows(at: [indexPath], with: .fade)

        } else if editingStyle == .insert {
            // Not used in our example, but if you were adding a new row, this is where you would do it.
        }
    }

}

上面代码中使行删除成为可能的单一关键方法是最后一个。这里再次重申:

// this method handles row deletion
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {

    if editingStyle == .delete {

        // remove the item from the data model
        animals.remove(at: indexPath.row)

        // delete the table view row
        tableView.deleteRows(at: [indexPath], with: .fade)

    } else if editingStyle == .insert {
        // Not used in our example, but if you were adding a new row, this is where you would do it.
    }
}

故事板

在故事板中的视图控制器中添加一个UITableView。使用自动布局将表格视图的四个边缘固定到视图控制器的边缘。从故事板中的表格视图控件,通过控制拖拽操作,连接到代码中的@IBOutlet var tableView: UITableView!

完成

就是这样。您现在应该可以运行您的应用程序,并通过向左滑动并点击“删除”来删除行。


变体

更改“删除”按钮文本

输入图像描述

添加以下方法:

func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
    return "Erase"
}

自定义按钮操作

输入图像描述

添加以下方法。

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {

    // action one
    let editAction = UITableViewRowAction(style: .default, title: "Edit", handler: { (action, indexPath) in
        print("Edit tapped")
    })
    editAction.backgroundColor = UIColor.blue

    // action two
    let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: { (action, indexPath) in
        print("Delete tapped")
    })
    deleteAction.backgroundColor = UIColor.red

    return [editAction, deleteAction]
}

请注意,此功能仅适用于iOS 8及以上版本。有关更多详细信息,请参见此答案

已更新至iOS 11

在iOS 11中,可以使用添加到UITableViewDelegate API的方法将操作放置在单元格的前导或后继位置。

func tableView(_ tableView: UITableView,
                leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
 {
     let editAction = UIContextualAction(style: .normal, title:  "Edit", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
             success(true)
         })
editAction.backgroundColor = .blue

         return UISwipeActionsConfiguration(actions: [editAction])
 }

 func tableView(_ tableView: UITableView,
                trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?
 {
     let deleteAction = UIContextualAction(style: .normal, title:  "Delete", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
         success(true)
     })
     deleteAction.backgroundColor = .red

     return UISwipeActionsConfiguration(actions: [deleteAction])
 }

更多阅读材料


感谢您提供的示例和代码。我现在准备实现删除功能。您能告诉我为什么要在viewDidLoad()中添加"self.tableView.registerClass(..."这一行代码的目的是什么吗?在界面构建器中有没有相应的等效方法?在自定义单元格示例中没有出现过这个问题。现在我们好像重复指定了cellReuseIdentifier两次。谢谢! - rockhammer
如果包括.registerClass行,编译将失败。 - rockhammer
如何实现向右滑动?比如说向左滑动“拒绝”某个单元格中的内容,而向右滑动“接受”某个单元格中的内容呢? - Munib
1
据我所知,@return0,右滑功能不是内置的,因此您需要从头开始创建它。如果您想尝试,请参阅此文章以获取启动的想法。但是,我不建议这样做,因为这不是用户预期的标准操作。相反,我会在左滑时显示两个按钮选择,就像我上面答案中的自定义按钮操作部分一样。 - Suragch
@Suragch 这个答案帮助我解决了《大妈党的iOS编程第6版》第11章的青铜挑战。 - ZacSketches
显示剩余5条评论

72

这段代码演示了如何实现删除操作。

#pragma mark - UITableViewDataSource

// Swipe to delete.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        [_chats removeObjectAtIndex:indexPath.row];
        [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    }
}

如果需要,在您的初始化覆盖函数中,添加以下行以显示“编辑”按钮项:

self.navigationItem.leftBarButtonItem = self.editButtonItem;

你需要实现那个方法。内部的内容应该与你的用例相匹配。在上面的代码中,_chats是表视图的后备数据。一旦用户点击删除,应该从_chats中删除单个聊天对象,以便数据源反映新的行数(否则会抛出异常)。 - ewcy

26

我遇到了一个问题,但刚刚解决了,所以想分享一下,说不定能帮到别人。

我有一个UITableView,并添加了以下方法,以启用滑动删除:

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    // Return YES if you want the specified item to be editable.
    return YES;
}

// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        //add code here for when you hit delete
    }    
}
我正在开发一个更新,使我能够将表格放入编辑模式并启用多选。为此,我添加了来自苹果TableMultiSelect示例的代码。一旦我做到了这一点,我发现我的滑动删除功能停止工作了。 结果发现在viewDidLoad中添加以下行是问题的原因:
self.tableView.allowsMultipleSelectionDuringEditing = YES;

加入这行代码后,多选功能可以使用,但滑动删除功能无法使用。相反地,不加这行代码时,则是滑动删除功能可用,但多选功能无法使用。

解决方案:

在你的viewController中添加以下方法:

- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
    self.tableView.allowsMultipleSelectionDuringEditing = editing; 
    [super setEditing:editing animated:animated];
}

然后,在您的将表格置于编辑模式的方法中(例如从按钮按下),您应该使用:

[self setEditing:YES animated:YES];

改为:

[self.tableView setEditing:YES animated:YES];

这意味着只有当表格处于编辑模式时,多选才会被启用。


这很有帮助。我在Storyboard中设置了allowsMultipleSelection。这解决了问题。 - Mark Suman
1
这解决了一个让我们抓狂的问题。我现在明白,“滑动删除”和“编辑模式下批量删除”基本上是互相排斥的,你必须在进入/离开编辑模式时进行控制。非常感谢您对此进行研究! - fbitterlich

19

下面的UITableViewDataSource将帮助您进行滑动删除

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    // Return YES if you want the specified item to be editable.
    return YES;
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        [arrYears removeObjectAtIndex:indexPath.row];
        [tableView reloadData];
    }
}

arrYears 是一个 NSMutableArray,然后重新加载 tableView。

Swift

 func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
            return true
        }

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if editingStyle == UITableViewCellEditingStyleDelete {
        arrYears.removeObjectAtIndex(indexPath.row)
        tableView.reloadData()
    }
}

但它是UITableViewDataSource。 - HotJard

19

在iOS 8和Swift 2.0中,请尝试以下方法:

override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
   // let the controller to know that able to edit tableView's row 
   return true
}

override func tableView(tableView: UITableView, commitEdittingStyle editingStyle UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)  {
   // if you want to apply with iOS 8 or earlier version you must add this function too. (just left in blank code)
}

override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]?  {
   // add the action button you want to show when swiping on tableView's cell , in this case add the delete button.
   let deleteAction = UITableViewRowAction(style: .Default, title: "Delete", handler: { (action , indexPath) -> Void in

   // Your delete code here.....
   .........
   .........
   })

   // You can set its properties like normal button
   deleteAction.backgroundColor = UIColor.redColor()

   return [deleteAction]
}

这是一个很好的答案,你可以使用它设置多个操作。 - Munib

12

@Kurbz的回答很棒,但我想留下这个注释,并希望这个答案能节省一些人的时间。

我在控制器中偶尔会出现这些行,它们会导致滑动功能无法正常工作。

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
    return UITableViewCellEditingStyleNone; 
}

如果您使用UITableViewCellEditingStyleInsertUITableViewCellEditingStyleNone作为编辑样式,则滑动功能不起作用。您只能使用UITableViewCellEditingStyleDelete,这是默认样式。


1
在我的情况下,我想要能够滑动删除,但同时也能够移动我的单元格。可移动的单元格还会在单元格的左侧显示“删除”按钮,这与我的设计不符,为了去除它,编辑样式应该是.none。我通过“if tableView.isEditing { return .none } else { return .delete }”来解决这个问题。 - user2359168
救了我的命啊,伙计。谢谢 :) - Sourav Chandra

10

Swift 4

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
    let delete = UITableViewRowAction(style: .destructive, title: "delete") { (action, indexPath) in
        // delete item at indexPath
    tableView.deleteRows(at: [indexPath], with: .fade)

    }
    return [delete]
}

1
好的,这样可以让删除选项卡出现,但是当你按下它时并不会删除它。你需要在数据源中删除对象并重新加载表格,对吗? - user3069232
是的,"// delete item at indexPath" 根据indexPath放置您的删除行逻辑。 - Pratik Lad
已在iOS 14中被弃用。 - Zorayr

9
此外,可以使用以下方法在SWIFT中实现此功能。
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if (editingStyle == UITableViewCellEditingStyle.Delete){
        testArray.removeAtIndex(indexPath.row)
        goalsTableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
    }
}

9

Swift 3

您所需要做的就是启用这两个函数:

func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {

    return true

}

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {

    if editingStyle == UITableViewCellEditingStyle.delete {
        tableView.reloadData()
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,