Wednesday, June 5, 2019

Handle Edit Mode in iOS UITableView

iOS UITableView in iOS has build-in support for editing on both tableview level and tableview cell level, many common operation can be handled in TableView's edit mode.

1. Set Edit Mode in TableView level
UITableView has a isEditing property, just setting the property to true will put tableview into edit mode. By default, a delete button shows on left side of each rows as shown below.


The edit icon can be customized as delete,  insert, or nothing on the left side of the row in edit mode. This can be set for each row by calling UITableViewDelegate editingStyleForRowAt method as shown below

override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        if indexPath.row % 3 == 0 {
            return .delete
        }
        else if indexPath.row % 3 == 1 {
            return .insert
        }

        else {
            return .none
        }
   }



In case, a particular row cannot be edited, then it can be customized by UITableViewDataSource canEditRowAt method as below
verride func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        // Return false if you do not want the specified item to be editable.
        if indexPath.row  == 3 {
            return false
        }
        else {
            return true
        }

    }

After user clicks the delete button or insert icon, UITableViewDataSource.commiteditingStyle method will be called to let application handle the action. After the action is handled, the tableview is still staying in the edit mode.

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            // Delete the row from the data source
           print("delete button clicked at \(indexPath.section)\\\(indexPath.row)")
        } else if editingStyle == .insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
            print("insert button clicked at \(indexPath.section)\\\(indexPath.row)")
        }
    }

If the TableView is embedded in navigation viewcontroller, then there is build-in edit button that shows on top right side of the navigation bar, use the below code to enabled it in viewDidLoad method.

   self.navigationItem.rightBarButtonItem = self.editButtonItem
When user clicks the edit button, the TableView.isEditing is set to true automatically, and the button
title is changed to Done. Clicking Done button will set isEditing to false and show Edit title for the button again.

 

2. Show Reorder control in TableView Edit mode
In addition to delete or insert, UITableView also show a reorder icon (two horizontal lines on right side) in Edit mode as below


In order to show the reorder button in table view's edit mode, UITableViewDataSource.moveRowAt method must be implemented. (It is not needed to check the checkbox of "Shows Re-order Controls" on tableview cell settings from storyboard editor).
After user moves a row to a different position, UITableViewDataSource.moveRowAt method will be called to let application handle the move result as below.

  override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        print("moveRowAt called from \(sourceIndexPath) to \(destinationIndexPath)")
    }


3. Edit mode for single row
In addition to set the edit mode at TableView level, user can also set a single tableview row into Edit mode by swiping left to right, or right to left on a single row.

By default, the delete edit style is used when user swipes from right to left on a cell. This can be enabled by implementing UITableViewDataSource.commitEditingStyle method, so UITableView knows how to handle the delete operation, the method is called when user clicks the delete button.

  // Override to support editing the table view.
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        print("commitEditingStyle called at \(indexPath)")
        if editingStyle == .delete {
            // Delete the row from the data source
           //tableView.deleteRows(at: [indexPath], with: .none)
           print("delete button clicked at \(indexPath.section)\\\(indexPath.row)")
        } else if editingStyle == .insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
            print("insert button clicked at \(indexPath.section)\\\(indexPath.row)")
        }

    }

If you do not want to show the delete button on a particular row, you can customize it by editingStyleForRowAt method, and return .none for the row. Note for single row edit mode, .insert is not supported.
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
        print("editingStyleForRowAt called for \(indexPath)")
        
        if indexPath.row % 3 == 0 {
            return .delete
        }
        else if indexPath.row % 3 == 1 {
            return .insert
        }
        else {
            return .none
        }

   }


Starting from iOS 11, two new API were added in UITableViewDelegate to allow developer to customize the button when user swipes a tableview row from left to right, or from right to left. 
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration?

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

The below code shows two buttons when user swipes left to right. These two method works without the need to customize any other UIWebView method. The below code shows two button on left side of the tableview row.

override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        print("leadingSwipeActionsConfigurationForRow called, is Editing \(tableView.isEditing)")
        
            let add = UIContextualAction(style: .normal, title: "add2") { (action, view, completion ) in
            print("add2 called, table is Editing \(tableView.isEditing)")
            tableView.isEditing = false
            
        }
        
        let delete = UIContextualAction(style: .destructive, title: "delete2") { (action, view, completion ) in
            print("delete button clicked, is Editing \(tableView.isEditing)")
            tableView.isEditing = false
        }
        let config = UISwipeActionsConfiguration(actions: [add, delete])
        return config
    }

If trailingSwipeActionsConfigurationForRowAt is not implemented, then the default delete button will show when user swipes from right to left.

3 comments: