Self-sizing Table View Cells

In this tutorial, you’ll learn how to enable self-sizing table view cells, as well as how to make them resize on-demand and support Dynamic Type. By Kevin Colligan.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Creating Dynamic Height

If you recall from the beginning, selecting an auteur presents a view controller that shows the selected auteur’s films. The cells in this table view will need to have dynamic height as well.

The first step, just like before, is to create another subclass of UITableViewCell.

In the project navigator select the Views group and press Command-N to create a new file in this group. Create a new Cocoa Touch Class called FilmTableViewCell and make it a subclass of UITableViewCell.

Open FilmTableViewCell.swift and, like before, delete the two automatically generated methods in FilmTableViewCell and then add these properties:

@IBOutlet weak var filmImageView: UIImageView!
@IBOutlet weak var filmTitleLabel: UILabel!
@IBOutlet weak var moreInfoTextView: UITextView!

Open Main.storyboard and select the cell in the table view in the Auteur Detail View Controller scene. Set the Custom Class of the cell to FilmTableViewCell, and then change the row height to 300 to give yourself plenty of room to work with.

Now, drag out an Image View, a Label and a Text View. Place them as pictured below (the text view is on the very bottom):

Auteur Detail View Controller

Change the text of the text view to Tap For Details > and the label to Name. Change the mode of the image view to Aspect Fit. Select the text view, in the Attribute inspector. Then, change its alignment to Centered, set the text color to Red and disable scrolling:

No Scroll

Disabling scrolling is of similar importance to setting a label to 0 lines. With scrolling disabled, the text view knows to grow its size to fit all of its content, since the user won’t be able to scroll through the text.

A bit further down past where you disabled scrolling, remove the check from User Interaction Enabled. This will allow touches to pass through the text view and trigger a selection of the cell itself. Then, set the background color to Clear Color.

More tweaks

Now, select the cell in Auteur Detail View Controller scene and set the background to the Hex Color #161616, just as you did on the Auteur scene. Select the name label and set the text color to white, so you can see it on the Main.storyboard.

Connect the three elements with their corresponding outlets, as you did with the first cell.

Now, you’ll add constraints. Starting with the text view and moving up:

  • Pin the bottom edge of the text view 0 points from the bottom margin of the content view.
  • Pin the leading and trailing edges of the text view 8 points from the leading and trailing margins of the content view.
  • Pin the top edge of the text view 8 points from the bottom of the label.
  • Pin the top edge of the label 8 points from the bottom of the image view.
  • Center the name label by choosing Horizontally in Container in the Align menu.
  • Select both the name label and image view (using Shift-click) and choose Equal Widths from the Pin menu.
  • Pin the top edge of the image view 0 points from the top margin of the content view.
  • Pin the leading and trailing edges of the image view 8 points from the leading and trailing margins of the content view

You’re done with the storyboard for now. Just as you had to do with the previous view controller, dynamic cell heights take a bit of code as well.

Open AuteurDetailViewController.swift and replace tableView(_:cellForRowAt:) with the following:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) 
    -> UITableViewCell {
  let cell = tableView
    .dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! FilmTableViewCell
  let film = selectedAuteur.films[indexPath.row]
  cell.filmTitleLabel.text = film.title
  cell.filmImageView.image = UIImage(named: film.poster)
  cell.filmTitleLabel.textColor = .white
  cell.filmTitleLabel.textAlignment = .center
  cell.moreInfoTextView.textColor = .red
  cell.selectionStyle = .none
  
  return cell
}

This should look very familiar by now. You are dequeuing and casting your cell, getting a reference of the model struct you are going to display and then configuring your cell before you return it.

Now, in viewDidLoad() of the same class, add the following code to the end of the method:

tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 300

Build and run your app. Click through one of the auteurs to see their films.

Auteur Detail View Controller

Not bad, but take it to the next level by adding expanding cells to reveal more info about each work. Your client is going to love this!

Expanding Cells

Since your cell heights are driven by Auto Layout constraints and the content of each interface element, expanding the cell should be as simple as adding more text to the text view when the user taps that cell.

Open AuteurDetailViewController.swift and add the following extension:

extension AuteurDetailViewController: UITableViewDelegate {
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
  // 1
  guard let cell = tableView.cellForRow(at: indexPath) as? FilmTableViewCell else {
    return
  }
  
  var film = selectedAuteur.films[indexPath.row]

  // 2
  film.isExpanded = !film.isExpanded
  selectedAuteur.films[indexPath.row] = film

  // 3
  cell.moreInfoTextView.text = film.isExpanded ? film.plot : moreInfoText
  cell.moreInfoTextView.textAlignment = film.isExpanded ? .left : .center
  cell.moreInfoTextView.textColor = film.isExpanded ? 
    UIColor(red:0.75, green:0.75, blue:0.75, alpha:1.0) : 
    .red

  // 4
  tableView.beginUpdates()
  tableView.endUpdates()

  // 5
  tableView.scrollToRow(at: indexPath, at: .top, animated: true)
 }
}

Here’s what’s happening:

  1. You ask the tableView for a reference of the cell at the selected indexPath and then to get the corresponding Film object.
  2. You toggle the isExpanded state of the Film object and add it back into the array, which is necessary since structs are value types.
  3. Next, you alter the text view of the cell, depending on whether the work is expanded. If it is, you set the text view to display the film’s info property. You also change the text alignment to .left and its color to light gray. If it isn’t expanded, you set the text back to “Tap for Details >”, the alignment back to .center and the color back to red.
  4. The table view needs to refresh the cell heights now. Calling beginUpdates() and endUpdates() will force the table view to refresh the heights in an animated fashion.
  5. Finally, you tell the table view to scroll the selected row to the top of the table view in an animated fashion.

Now in tableView(_:cellForRowAt:), add the following three lines at the end, before you return the cell:

cell.moreInfoTextView.text = film.isExpanded ? film.plot : moreInfoText
cell.moreInfoTextView.textAlignment = film.isExpanded ? .left : .center
cell.moreInfoTextView.textColor = film.isExpanded ? 
  UIColor(red:0.75, green:0.75, blue:0.75, alpha:1.0) : 
  .red

This code will cause a cell that is being reused to correctly remember if it was previously in the expanded state.

Build and run the app. When you tap a film cell, you’ll see that it expands to accommodate the full text. But the image animates a bit weirdly.

That won’t be hard to fix! Open Main.storyboard, select the image view in your FilmTableViewCell and open the Size inspector. Change the Content Hugging Priority and Content Compression Resistance Priority to the values pictured below:

Hugs

Setting the Vertical Content Hugging Priority to 252 will help the image view to hug its content and not to get stretched during the animation. Setting the Vertical Compression Resistance Priority to 1000 prevents the image from being compressed if other interface elements grow around it.

Build and run the app. Select an auteur and tap on the films. You should see some very smooth cell expansion, revealing information about each film.