Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expand and contract tableview cell when tapped, in swift

I'm trying to expand a tableview cell when tapped. Then, when it is tapped again, I want it to go back to its original state.

If cellA is expanded and cellB is tapped, I want cellA to contract and cellB to expand at the same time. So that only one cell can be in its expanded state in any given moment.

My current code:

class MasterViewController: UITableViewController {
   var isCellTapped = false
   var currentRow = -1


   override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        selectedRowIndex = indexPath
        tableView.beginUpdates()
        tableView.endUpdates()
    }


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

    if indexPath.row == selectedRowIndex.row {
        if isCellTapped == false {
            isCellTapped = true
            return 140
        } else if isCellTapped == true {
            isCellTapped = false
            return 70
        }

    }

    return 70
}

Current code works well when:

  • You tap a row (it expands)
  • You tap it again (it contracts)

It fails when:

  • You tap a row (it expands)
  • You tap another row (it contracts, but the other row does not expand)

How can I solve this?

like image 248
Diego Petrucci Avatar asked Aug 04 '15 19:08

Diego Petrucci


People also ask

How do I add a space between cells in tableView?

The way I achieve adding spacing between cells is to make numberOfSections = "Your array count" and make each section contains only one row. And then define headerView and its height. This works great.


2 Answers

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.

like image 68
Victor Sigler Avatar answered Oct 21 '22 15:10

Victor Sigler


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.

like image 39
Mark Kazakov Avatar answered Oct 21 '22 15:10

Mark Kazakov