Igor Kulman

Using reordering and selection at the same time in UITableView

· Igor Kulman

When you use an UITableView you usually either use it with selection in normal mode or you switch it to edit mode to do reordering and deleting of the items.

In a recent project I had to use an UITableView with items that would be selectable and could be reordered at the some time.

Reordering

To make things a bit more complicated only one section of the UITableView had to have the possibility to reorder items and only inside this section, the user should not have been able to drag an item to another sections while reordering.

Reordering

To implement those requirement you first have to tell the UITableView which cells can be reordered

func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
    // some condition depending on indexPath.section
}

This by itself is not enough because we will not use the UITableView in edit mode, you need to explicitly show the reorder handle for those cells

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    cell.showsReorderControl = self.tableView(tableView, canMoveRowAt: indexPath)
}

Now you can process the result of user reordering the cells

func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    // update the model, etc
}

The final step is to make sure the user can reorder the cells only in one specific section and cannot drag any cell to another section

func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
    guard sourceIndexPath.section != proposedDestinationIndexPath.section else {
        // moving inside the same section is always ok
        return proposedDestinationIndexPath
    }

    // use the last item of the same section when trying to move to the next one
    if sourceIndexPath.section < proposedDestinationIndexPath.section {
        return IndexPath(
            row: tableView.numberOfRows(inSection: sourceIndexPath.section) - 1,
            section: sourceIndexPath.section
        )
    }

    // use the first item of the same section when trying to move to the previous one
    return IndexPath(row: 0, section: sourceIndexPath.section)
}

This implementation also guides the user moving the cell either to the top or bottom of the allowed section when trying to drag the cel outside of the section.

See also