UITableView Swipe to Delete Action using Swift iOS 17.07.2020

iOS 11 brought a new way to add custom swipe swipe actions to UITableViewCell's via the new UISwipeActionsConfiguration class and associated UITableViewDelegate methods.

When adding swipe actions you can add either leading swipe actions, trailing swipe actions or both kinds of swipe actions. The keys to adding swipe actions start with new UITableView delegate methods, defined as follows:

func tableView(UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
    -> UISwipeActionsConfiguration?
func tableView(UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
    -> UISwipeActionsConfiguration?

As you can see in the method signatures, these both return a UISwipeActionsConfiguration class. This class is initialized with an array of UIContextualAction classes.

For this tutorial you will need to setup a UITableView. If you don't have one setup you can take a look at this tutorial.

Let’s take a deeper dive into the trailing actions method, since it will apply more than one possible swipe action.

                                    
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
    let deleteAction = self.contextualDeleteAction(forRowAtIndexPath: indexPath)
    let flagAction = self.contextualFlagAction(forRowAtIndexPath: indexPath)
    let swipeConfig = UISwipeActionsConfiguration(actions: [deleteAction, flagAction])
    return swipeConfig
}

Here, we are using a helper method to instantiate each of the UIContextualAction classes we will need to build our swipe actions. Then we can instantiate a UISwipeActionsConfiguration using the UIContextualAction classes to populate the actions array. The UISwipeActionsConfiguration is then returned from the delegate method.

If you do not want an action to run with a full swipe, you can add the following line of code just below the where we created the configuration:

configuration.performsFirstActionWithFullSwipe = false

This will prevent the first action from running when someone does a full swipe.

Next, lets take a look at the UIContextualAction. This is where the work happens. The initializer takes a UIContextualAction.Style, title, and UIContextualActionHandler block. The UIContextualActionHandler gives you access to the UIContextualAction, the view that displayed the action, and a completion handler that you pass a bool indicating whether or not the action was successful.

func contextualFlagAction(forRowAtIndexPath indexPath: IndexPath) -> UIContextualAction {
    let item = items[indexPath.row]
    let lastChar = Int(String(item.last!)) ?? 0
    let less5 = lastChar < 5

    let action = UIContextualAction(style: .normal, title: "Flag") { (action, view, completion) in
        if less5 {
            self.items[indexPath.row] = "\(item) + "
            self.tbl.cellForRow(at: indexPath)?.accessoryType = .checkmark
            self.tbl.reloadRows(at: [indexPath], with: .none)
            completion(true)
        } else {
            completion(false)
        }
    }
    action.image = UIImage(named: "flag")
    action.backgroundColor = less5 ? UIColor.gray : UIColor.orange
    return action
}

func contextualDeleteAction(forRowAtIndexPath indexPath: IndexPath) -> UIContextualAction {
    let action = UIContextualAction(style: .destructive, title: "Delete") { (action, view, completion) in
        self.items.remove(at: indexPath.row)
        self.tbl.deleteRows(at: [indexPath], with: .automatic)
        //self.updateHeaderNumber(to: self.notifications.count)
        completion(true)
    }

    action.backgroundColor = .systemRed
    action.image = UIImage(named: "delete")
    return action
}