A nifty feature of the UItableView rows is the possibility to add custom actions: when the user swipes on a row from right to left the system will display a list of buttons representing the different available actions for that row.

Swipe action

As you can see actions are a very useful feature for your table views, and they are very easy to implement. But the above example has a few caveats:

  • The buttons display only text: no icons
  • There is no left swipe available

What I would like is for my list to look like the ios email application: with swiping both left, and right, and display icons along the text for the action.

Letś see how we can do that.

Introduction: adding swipe actions to a UItableView row

First thing we will se how to implement, plain, vanilla actions like the ones in the image: after that we will see how the original version can be improved to add better functionality.

As an example I will use a TableView displaying a list of todo notes. For this tutorial how we got the data is not important. we will merely assume we start from a code containing a TableView whose data is being fetched remotely via REST.

Our starting code, displaying a mere list with no special features whatsoever, would look like this:

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    var datasource:[Todo] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        TodoService.sharedInstance.getTodos().then { resultData -> Void in
                self.datasource = resultData
                self.tableView.reloadData()
            }.catch { error in
                print(error.localizedDescription)
        }
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return datasource.count
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    //MARK:-cellForRowAt
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cellTodo", for: indexPath)
        cell.textLabel?.text = datasource [indexPath.row].title
        return cell
    }
}

You can dig into the part about fetching data from a Rest service, or about the use of promises if you want, but if you feel comfortable with that part of the code, we can move to the model of the todo object being rendered on the view, since we will be using soon that knowledge to implement our first version of table actions.

class Todo:  Mappable
{
    var userId: Int?
    var id: Int?
    var title: String?
    var completed: Bool?
   // ...
}

I want to add the following actions to my todo list:

  • An action to change the status of the todo from incomplete to complete (and viceversa).
  • An action send the todo by email.

In order to do that I need to implement the method editActionsForRowAt in the UITableViewDelegate:

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

let status = datasource [indexPath.row].completed!
let email = UITableViewRowAction(style: .normal, title: "Email") { (action, indexPath) in
// code to email the todo goes here
}

let updateStatus = UITableViewRowAction(style: .normal, title: (status ? "Open" : "Complete")) { (action, indexPath) in
// code to implement the status update goes here
}

updateStatus.backgroundColor = (status ? UIColor.blue : UIColor.green)

return [email, updateStatus]
}

Notice the following:

  • You can set the background color of the actions.
  • Different rows have different actions depending on the todo they are displaying: incomplete todos should offer the mark as complete action, and completed todos should offer the action to reopen them.

Adding left swipe functionality with SwipeCellKit

If we want to add left swipe functionality to our example, we can either implement it though the use of UISwipeGestureRecognizer, or use a third party library that provides that functionality.

For this example I will use SwipeCellKit a library that does not only provides the swipe functionality, but includes other attractive features such as the possibility to add images to our action buttons, animations, etc

For starters we need to install SwipeCellKit using, by example, CocoaPods:

use_frameworks!
# Pods for TestSwipe
    pod 'SwipeCellKit'

We must delete the function that we implemented before:

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

Since we will be replacing it by implemented a new function in the SwipeTableViewCellDelegate provided by SwipeCellKit:

extension ViewController: SwipeTableViewCellDelegate {

func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {

        let status = datasource [indexPath.row].completed!
        let email = SwipeAction(style: .default, title: "Email") { (action, indexPath) in
            // code
        }
        email.image = UIImage(named: "mail")
        email.backgroundColor = UIColor.darkGray

        let updateStatus = SwipeAction(style: .default, title: (status ? "Open" : "Close")) { (action, indexPath) in
            // updateStatus
        }
        updateStatus.image = (status ? UIImage(named: "reopen") : UIImage(named: "done"))
        updateStatus.backgroundColor = (status ? UIColor.brown : UIColor.blue)

        return  (orientation == .right ?  [email] : [updateStatus])
    }
}

Notice the following:

  • This function returns an array of SwipeAction
  • We can place actions both left, and right following the orientation attribute (return (orientation == .right ? [email] : [updateStatus]))
  • A SwipeAction has an image property where we can set the icon for the button action

Lastly we have to modify the cellForRowAt method:

  • Our dequeued cell must be a SwipeTableViewCell cell
  • We must set the delegate property on SwipeTableViewCell
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // cell must be of type as! SwipeTableViewCell
        let cell = tableView.dequeueReusableCell(withIdentifier: "cellTodo", for: indexPath) as! SwipeTableViewCell
        cell.delegate = self
        cell.textLabel?.text = datasource [indexPath.row].title
        return cell
    }

The result: the email action is accessible through a right swipe:

Right Swipe

Our action to mark the todo as complete/incomplete (depending on the status) is accesible through a left swipe:

Left Swipe

Both actions have icons: the addition of icons, and placing the actions grouped by functionality right, and left provides a friendly user interface for the end user.

Configuring the actions behavior

As I said before by using the SwipeCellKit you gain more than left swipe gestures, you can configure stuff like the expansion style, or transition style:

  • expansion style: when the user finish the swipe movement we can automatically set the row to be deleted (like the ios email app feature), or to automatically launch the first action (reducing the number of clicks needed by the user to execute the action)
  • transition style: is used to indicate how the buttons are displayed during the swipe movement (a look and feel feature)
func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeTableOptions {

        var options = SwipeTableOptions()
        options.expansionStyle = .selection
        options.transitionStyle = .border
        return options
    }

This is all :-).  Hope it was of help!

Post: iOS 11 includes support for left, and right swipes without having to pass through gesture recognizers. You can see how to implement left/right swipe in iOS 11 here.

If you liked this post, you can subscribe to my blog (click at the follow button at the bottom ) so you can be notified of new posts.

Advertisements