'UITableViewRowAction vs UISwipeActionsConfiguration

The two APIs seem to get the same result. In which case is it better to use one over the other?

override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
  let deleteAction = UITableViewRowAction(style: .destructive,
                                          title: "Delete") { (action, indexPath) in
    print("DELETE")
  }
  return [deleteAction]
}

override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
  let DeleteAction = UIContextualAction(style: .destructive, title: "Delete", handler: { (action, view, success) in
      print("Delete")
  })
  DeleteAction.backgroundColor = .red
  return UISwipeActionsConfiguration(actions: [DeleteAction])
}


Solution 1:[1]

It does basically the same, but swipe actions are available since iOS 11 was released and have some new features:

  • You are able to set actions for trailing swipe as well as for leading swipe
  • You can set image of action action.image = UIImage(...). If there is enough space it shows image as well as title enter image description here

Also, you should use swipe actions because they are preferred and in the future updates UITableViewRowActions will be deprecated how UITableView header comment can tell us:

Use -tableView:trailingSwipeActionsConfigurationForRowAtIndexPath: instead of this method, which will be deprecated in a future release.

Solution 2:[2]

Here is the code which is working for me

internal func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

   // let delete = UITableViewRowAction(style: .destructive, title: "Delete") { (action, indexPath) in
    let contextItem = UIContextualAction(style: .destructive, title: "") {  (contextualAction, view, boolValue) in
    // delete item at indexPath
       //if self.isfilterenabled == true {
         //   return
        //}


        if self.isfilterenabled == true {
            //entryFilter.filteredEntries[indexPath.row]
            self.entryFilter.filteredEntries.remove(at: indexPath.row)
        } else {
            self.data.remove(at: indexPath.row)
        }

        self.table.deleteRows(at: [indexPath], with: .fade)
        self.save()
    }



    //let share = UITableViewRowAction(style: .normal, title: "SavePDF") { (action, indexPath) in
        // share item at indexPath
    let contextItemSave = UIContextualAction(style: .normal, title: "") {  (contextualAction, view, boolValue) in





        let alert  = UIAlertController(title: "Done! ", message: "PDF has been saved " ,preferredStyle: .alert)

        let okAction = UIAlertAction(title: "OK ", style: .default, handler: nil)
        alert.addAction(okAction)

        alert.popoverPresentationController?.sourceView = self.view // so that iPads won't crash

        // exclude some activity types from the list (optional)
        //activityViewController.excludedActivityTypes = [ UIActivityTypeAirDrop, UIActivityTypePostToFacebook ]

        // present the view controller
        self.present(alert, animated: true, completion: nil)
        // present(alert, animated : true, completion : nil )

    }

   // share.backgroundColor = UIColor.blue

    contextItemSave.image = UIImage(named:"PDF.jpg")
    contextItem.image = UIImage(named:"delete.jpg")
    let swipeActions = UISwipeActionsConfiguration(actions: [contextItem,contextItemSave])

    return swipeActions
    //return [delete, share]
}`

Solution 3:[3]

I have created an extension for UISwipeActionsConfiguration which you can use if you have a sizing issue with your image. Basically, the idea is to create an Attributed String from the image and the text and set it to label and create an image from that label. And append it to UIContextualAction's image property.

extension UISwipeActionsConfiguration {

    public static func makeTitledImage(
        image: UIImage?,
        title: String,
        textColor: UIColor = .white,
        font: UIFont = .systemFont(ofSize: 14),
        size: CGSize = .init(width: 50, height: 50)
    ) -> UIImage? {
        
        /// Create attributed string attachment with image
        let attachment = NSTextAttachment()
        attachment.image = image
        let imageString = NSAttributedString(attachment: attachment)
        
        /// Create attributed string with title
        let text = NSAttributedString(
            string: "\n\(title)",
            attributes: [
                .foregroundColor: textColor,
                .font: font
            ]
        )
        
        /// Merge two attributed strings
        let mergedText = NSMutableAttributedString()
        mergedText.append(imageString)
        mergedText.append(text)
        
        /// Create label and append that merged attributed string
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: size.width, height: size.height))
        label.textAlignment = .center
        label.numberOfLines = 2
        label.attributedText = mergedText
        
        /// Create image from that label
        let renderer = UIGraphicsImageRenderer(bounds: label.bounds)
        let image = renderer.image { rendererContext in
            label.layer.render(in: rendererContext.cgContext)
        }
        
        /// Convert it to UIImage and return
        if let cgImage = image.cgImage {
            return UIImage(cgImage: cgImage, scale: UIScreen.main.scale, orientation: .up)
        }
        
        return nil
    }
}

And you can use it like this;

public func tableView(
        _ tableView: UITableView,
        trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
    ) -> UISwipeActionsConfiguration? 
{
        let deleteAction = UIContextualAction(
            style: .normal,
            title:  nil,
            handler: { [weak self] (_, _, success: (Bool) -> Void) in
                success(true)
                print("Your action in here")
            }
        )
        
        deleteAction.image = UISwipeActionsConfiguration.makeTitledImage(
            image: UIImage(named: "delete_icon"),
            title: "Delete")
        )
        deleteAction.backgroundColor = .orange
        return UISwipeActionsConfiguration(actions: [deleteAction])
}

Solution 4:[4]

the method (editActionsForRowAt) is deprecated

The modern approach uses different method which is trailingSwipeActionsConfigurationForRowAt indexPath to provide actions for row when user swipes to the left.

There is also leadingSwipeActionsConfigurationForRowAt indexPath to provide actions when user swipes to the right.

here is example

func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
    -> UISwipeActionsConfiguration? {
    let deleteAction = UIContextualAction(style: .destructive, title: nil) { (_, _, completionHandler) in
        // delete the item here
        completionHandler(true)
    }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Zouhair Sassi
Solution 2 Guri S
Solution 3 ysnzlcn
Solution 4 Saad