Swift 3: Expandable Table View Cells

Hello everyone! It’s been a while since I posted my last Swift tutorial, decided that Christmas would be the perfect moment to do so.

As some of you may already noticed, expandable Table View Cells are very common across various iOS apps. One might think it must be very intrinsic part of TableView implemetation – this is wrong :)

There are many ways to do that.  AppCoda has featured very interesting and universal approach, but in some cases, where only few cells are required in one view, this seems to be overkill.

Another way is to use

insertRowsAtIndexPath()

But in my experience, it leads to terrible hassle with reusing cells. In other words, it gave me a headache to cover all cases when TableView was scrolled, reloaded, blah blah blah

I really like doing it by modyfing height constaint. However, before you read further, I’d like to familiarize you with pros and cons of such approach.

Pros

  • no hassle with with reusing cells
  • relatively simple to understand
  • quick to implement
  • sufficient in most cases

Cons

  • only for simple autolayout design
  • when height can’t be constant – ouch, that won’t work :(

 

Alright, that’s it for introduction. If you considered pros and cons and still want to learn how to do it, let’s dive in!

 

What we are going to build

 

It’s going to be a simple example with three Table View Cells with label and image. Whenever user taps on cell, it slides down showing the image. Pretty neat.

ezgif-com-video-to-gif

Interface setup

 

I assume you know how to build apps for iOS so I will not cover how to create project etc.

In your storyboard, make the following interface that consists of ViewController, UITableView and UITableViewCell with UILabel and UIImage. 

It should look like that:

zrzut-ekranu-2016-12-26-o-23-15-28

 

Set the constraints for the UILabel:

zrzut-ekranu-2016-12-26-o-23-18-38

and for the UIImage:

zrzut-ekranu-2016-12-26-o-23-18-49

 

And don’t forget to set Cell’s identifier to “ExpandableCell”. Ok, let’s move on

Custom UITableViewCell class

 

Create class named ExpandableCell and connect the image outlets. Don’t forget to connect NSLayoutConstraint as outlet as well.

zrzut-ekranu-2016-12-26-o-23-21-25

 

Your class should look the following way now:

next, we will add a boolean named isExpanded that indicates cell’s current state and adjusts imgHeightConstraint constant accordingly. Notice we implement property observer DidSet to handle the state of boolean.

 

that’s it folks, let’s move to the ViewController!

ViewController implementation

 

Connecting UITableView to our controller is pretty obvious, the same with  setting both delegate and dataSource to self, right?:)

For now, our ViewController should look like this:

 

We store each cell in a Set to keep track of boolean values that determines whether a cell is expanded or not, this is important when you have more cells and TableView has to reuse them.

Let’s see how dataSource should be implemented

 

 

Since I named my pictures “0”, “1”, “2”, I can use

to get their names for each cell. It’s worth to notice that I set isExpanded variable is set to boolean value that determines whether this cell is present is the Set that contains expanded cells. This way we keep track of expanded cells.

I suppose everything is clear in the code above, but delegate methods surely need more explanation

 

 

Let me tell you what is going on here.

We check if the cell is already expanded, if so we remove it from expandedRows Set, otherwise we insert it.  Then we switch isExpanded value to the opposite one and reload cell.

That’s it, we did it!

Improvements

 

I think it’s worth to implement one more feature. For now, to make the cell close, user has to tap  directly on this cell. To make it more user friendly, it would be good to allow tapping anywhere on other cell to roll up currently expanded one.

We will do it nearly the same way as we did above with one tiny difference

 

 

This time, whenever cell is deselected, isExpanded is set to false, instead of being set to the opposite boolean value. This prevent from situations when user closed a cell tapping on it and then selects other cell, which would result in expanding both of them.

 

When you test your app, you might have noticed the following warning in your console:

Will attempt to recover by breaking constraint

<NSLayoutConstraint:0x17409b6c0 UIImageView:0x100b04010.height == 128   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.

The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.

 

Hopefully, it’s very easy to fix. Just go to your storyboard and change priority of height constraint. 999 will be okay

zrzut-ekranu-2016-12-27-o-00-18-11

 

Voilà! Works like a charm!

Thanks for reading!

I strongly encourage you to share your insights in comments below. I’d love to hear your approach to deal with expandable cells!

Oh I would forget! Grab the full project here :)

Thanks for reading!

  • Pankaj Jakate

    I have implemented this but I am facing one issue for last 3 rows was not expanding and unable to see it. ( I have 13 row’s in table view, I have downloaded and changed the rows to 20 and it’s not working)
    Please help

    • Dawid Cedrych

      Hi, thanks for your comment! In fact you have just found a bug in my implementation, it does not work properly when there are enough rows to scroll down to them. It happens because the entire tableView gets reloaded, please give me some time to fix it. Thanks for reporting that bug!

      • Xavier Meslin

        hello, Did you manage to fix this issue ? Thank you

        • Dawid Cedrych

          Hi, failed to do it so far, it’s very tricky, working on it

        • Dawid Cedrych

          Guys, I fixed the bug, check out the updated post and source code!

  • Akram Khalifa

    Hi, Thanks for this tuto, i have a question, how can i select the expansion cell ?

    • Dawid Cedrych

      Hi, would like to select only the expanded part of a cell?

  • Joe Sene

    Great tutorial!!
    I have a question for you.
    After I expand the cell, I want to be able to interact with the cell before closing it again.
    Let’s say Its a rating cell and I want to expand the cell, give 3 stars to the product and after that close the cell…
    How could I do it?

    • Dawid Cedrych

      Hi Joe, thank you!:)
      In your use case I’d suggest to collapse cell only on deselection. It ensures user interaction with cell content will not be accidentally interrupted by collapsing cell. good UX first!:)
      In “didSelectRowAt” method delete “self.expandedRows.remove(indexPath.row)” and adjust the rest of method accordingly (switch statement will not be needed anymore)

  • Faydee

    Hello Dawid, thanks for writing the tutorial. Very helpful.

    I want to know is it possible to scroll after select specific cell?
    For example, when I select row 3, it will expand, but outside the screen, so I need to scroll down to see it, I want to scroll automatically.

    • Faydee

      Wow! I found it!
      Just add this line at the end of didSelectRow
      tableView.scrollToRow(at: indexPath, at: .top, animated: true)

  • Akhilendra Singh

    great tutorial. Thanks