iOS: How to build a Table View with Collapsible Sections

Part 2. Continue adopting Protocols and MVVM with Table Views

Stan Ostrovskiy
iOS App Development
6 min readMay 26, 2017

--

This is the second part of my Tutorial series on Table View with multiple cell types.

After reading the multiple responses and advice for the first part, I decided to add some major updates.

UITableViewController is changed to UIViewController with a TableView as a subview.

Now, ViewModel conforms to TableViewDataSource protocol. NumberOfRowsInSection, cellForRowAt, and numberOfSections are the part of ViewModel. This keeps the ViewController and ViewModel separated.

Please find the final updated project here.

Thanks everyone for the contribution!

In the first part we created the following Table View:

In this article, we will make some changes to have the section collapsible:

Table View With Collapsible Sections

To add the collapsible behavior, we need to know two things about the section:

  • is the section is collapsible or not
  • the current section state: collapsed/expanded

We can add both properties to existing ProfileViewModelItem protocol:

Note, that isCollapsible property only has a getter, because we will not need to modify it.

Next, we add a default isCollapsible value to the protocol extension. We set the default value to true:

Once you modified the protocol, you will see multiple compile errors in each of ProfileViewModelItems. Fix it by adding this property to each ViewModelItem:

These are all the changes we need to make in out ViewModel. The remaining part is to modify the View, so it can handle the collapse/expand actions.

There is no out-of-the-box way to add the collapsible behavior to the tableView, so we will mimic in a very simple way: when the section is collapsed, we will set its row count to zero. When it is expanded, we will use the default rowCount for this section. For the TableView we can provide this information in the numberOfRowsInSection method:

Now we need to create a custom header view, that will have the title and the arrow label. Create a subclass of UITableViewHeaderFooterView and set the layout in either xib or code:

We will use the section variable to store the current section index, that we will need later.

When the user taps on the section, the arrow view should rotate down. We can achieve that with a UIView Extension:

This is just one of the possible ways to animate the view rotation

Using this extension method, add the following code inside the HeaderView class:

When we call this method for collapsed state, it will rotate the arrow to the original position, for expanded state it will rotate the arrow to pi radians.

Next, we need to setup the current section title. As we did for the cells in the previous tutorial, create the item variable and use the didSet observer to set the title and the initial position of the arrow label:

The last questions are: how to detect the user tap on the header, and how to notify the TableView?

To detect a user interaction we can set a TapGestureRecognizer in our header:

To notify the TableView, we can use any of the ways I described here. In this case, I will use the delegation. Create a HeaderViewDelegate protocol with one method:

Add a delegate property inside the HeaderView:

Finally, call this delegate method from tapHeader selector:

The HeaderView is now ready to use. Let’s connect it to our ViewController.

Open ViewModel and make it conform to the TableViewDelegate:

Next, remove the titleForHeaderInSection method. Since we use a custom header, we will set a title another way:

For dequeueReusableHeaderFooterView to work, don’t forget to register headerView for the tableView

Once you set the headerView.delegate to self, you will notice the compiler error, because our ViewModel does not conform to the protocol yet. Fix it by adding another extension:

We need to set a way to reload the TableView section, so it will update the UI. In the more complex ViewModels that require to update, add or remove the tableViewRows, it will make sense to use a delegate with multiple methods. In our project we only need one method (ReloadSection), so we can us the callback:

Call this callback in toggleSection:

In ViewController we to use this callback to reload the tableView sections:

If you build and run the project, you will see this nice animating collapsing behavior.

You can check out the Final Project here.

There are some potential upgrades for this feature:

  1. Try to think of the way to only allow one section to be expanded. So when the user taps another section, it will first collapse the expanded one, and then expand the new one.
  2. When the section is expanding, scroll the tableView to show the last row in this section.

Please share your thoughts in the comments bellow, so we can discuss it.

Thanks for reading!

--

--