Expand and contract tableview cell when tapped, in swift

You need to take in account that you need to update your selected row when another is tapped, see the following code :

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {

    if indexPath.row == selectedRowIndex {
        return 140
    }
    return 44
}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

    if selectedRowIndex != indexPath.row {

        // paint the last cell tapped to white again
        self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: self.selectedRowIndex, inSection: 0))?.backgroundColor = UIColor.whiteColor()

        // save the selected index 
        self.selectedRowIndex = indexPath.row

        // paint the selected cell to gray
        self.tableView.cellForRowAtIndexPath(indexPath)?.backgroundColor = UIColor.grayColor()

        // update the height for all the cells
        self.tableView.beginUpdates()
        self.tableView.endUpdates()
    }
}

EDIT:

To handle that the cell where is selected and is tapped again return to its original state you need to check some conditions like the following:

var thereIsCellTapped = false
var selectedRowIndex = -1

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {

    if indexPath.row == selectedRowIndex && thereIsCellTapped {
        return 140
    }

    return 44
}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

    self.tableView.cellForRowAtIndexPath(indexPath)?.backgroundColor = UIColor.grayColor()

    // avoid paint the cell is the index is outside the bounds
    if self.selectedRowIndex != -1 {
        self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: self.selectedRowIndex, inSection: 0))?.backgroundColor = UIColor.whiteColor()
    }

    if selectedRowIndex != indexPath.row {
        self.thereIsCellTapped = true
        self.selectedRowIndex = indexPath.row
    }
    else {
        // there is no cell selected anymore
        self.thereIsCellTapped = false
        self.selectedRowIndex = -1
    }

    self.tableView.beginUpdates()
    self.tableView.endUpdates()
}

With the above modifications in yourdidSelectRowAtIndexPath and heightForRowAtIndexPath functions you can see when a cell is tapped its background color it will be changed to gray when it's height grow and when another cell is tapped the cell is painted to white and the tapped to gray and again and again allowing only tap one cell at time.

I though you can benefit and learn how to do a Accordion Menu in this repo I have created and I plan to update very soon to handle better results, it's handle using a UITableView just like you want.

Any doubt in the repository you can post it here.

I hope this help you.


A simple approach for expanding only one cell at a time:

Swift 3

class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {  

var selectedRowIndex = -1  
@IBOutlet weak var tableView: UITableView!

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if indexPath.row == selectedRowIndex {
            return 90 //Expanded
        }
        return 40 //Not expanded
    }

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if selectedRowIndex == indexPath.row {
            selectedRowIndex = -1
        } else {
            selectedRowIndex = indexPath.row
        }
        tableView.reloadRows(at: [indexPath], with: .automatic)
    }
}  

This code will expand the clicked cell and close previously open cell with animation.