UIKit Apprentice, Second Edition – Now Updated!

Learn iOS and Swift from scratch. Build four powerful apps—with support for iPad and Dark Mode. Publish apps to the App Store.

Home iOS & Swift Tutorials

Self-Sizing Table View Cells

Learn how to enable self-sizing table view cells and make them resize on-demand while supporting Dynamic Type.

5/5 5 Ratings

Version

  • Swift 5, iOS 14, Xcode 12
Update note: Chuck Krutsinger updated this tutorial for Xcode 12, iOS 14 and Swift 5. Joshua Greene wrote the original, and Kevin Colligan wrote an earlier update.

If you’ve ever created custom table view cells, chances are you’ve spent a lot of time sizing table view cells in code. Frankly, this approach is tedious, difficult and error-prone.

In this tutorial, you’ll learn how to create and size table view cells dynamically to fit their contents. You might think, “That’s going to take a lot of work!” Actually, it’s quite simple, as you’ll soon see. This tutorial will cover the following:

  • Creating table view cell layouts to size according to its content’s size.
  • Enabling dynamic type for more extensive app accessibility support.
  • Configuring Auto Layout to support dynamic cell sizes.
  • Enabling a cell to expand or collapse in size.
Note: This tutorial assumes you have a basic familiarity with Auto Layout and table views.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of this tutorial.

Imagine you have a movie-crazy client who wants an app to show off the work of their favorite film directors. And not just any directors: auteurs.

“Auteurs?” you ask, “That sounds French.”

Oui, c’est ça. The auteur theory of film making arose in France in the 1940s. It basically means the director is the driving creative force behind a film. Not every director is an auteur — only those who have powerful creative control over the finished movie. Think Tarantino or Scorsese.

“There’s one problem,” your client says. “We started making the app, but we’re stumped at how to display the content in a table view. Our table view cells have to resize (gulp!) dynamically! Can you make it work?”

You suddenly feel the urge to don a spiffy beret and start shouting orders!

Stylized cartoon image of a French man wearing a beret, on top of a orange star. Auteur.

Back in the days of iOS 6, Apple introduced a wonderful new technology: Auto Layout. Developers rejoiced; parties commenced in the streets; bands wrote songs to celebrate its greatness.

Or perhaps not, but they should have. Auto Layout was a big deal.

Flash forward to now. With all the improvements to Interface Builder, it’s easy to use Auto Layout to create self-sizing table view cells!

With a few exceptions, all you have to do is:

  1. Use Auto Layout for the UI elements inside the table view cells.
  2. Set the table view’s rowHeight to UITableView.automaticDimension.
  3. Set the estimatedRowHeight or implement the height estimation delegate method.

That’s what you’ll do to help your client.

Looking at the App’s Views

Open the Auteurs.xcodeproj in the starter folder. From the Project navigator, open Main.storyboard. You’ll see three scenes:

Auteurs scenes overview

From left to right, they are:

  • AuteurListViewController, a top-level navigation controller.
  • Auteurs Scene, which shows a list of auteurs.
  • Auteur Detail View Controller Scene, which displays the auteur’s films and information about each one.

Build and run.

You’ll see AuteurListViewController displaying a list of auteurs.

Auteurs list and details views with truncated information

Whoa! Wait a minute. Not only is the app missing images of each auteur, but the information you’re trying to display is also cut off. You can’t just increase the cell size and call it a wrap because each piece of information and each image will be a different size. Your cell heights need to change dynamically, depending on each cell’s content.

Don’t panique! Self-sizing cells are très faciles — very easy.

Creating Self-Sizing Table View Cells

You’ll start by implementing dynamic cell heights in AuteurListViewController. To get dynamic cell heights working properly, create a custom table view cell, then set it up with Auto Layout constraints.

In the Project navigator, open AuteurTableViewCell.swift. Add the following property:

@IBOutlet weak var bioLabel: UILabel!

This is where the author’s bio information will be displayed.

Next, open Main.storyboard. In the Auteurs Scene, select the AuteurCell in the table view. In the Identity inspector, set the class to AuteurTableViewCell:

Set AuteurTableViewCell class

Click the button above the storyboard layout to open the Library. Drag and drop a label into the cell. Set the text to Bio. Set the new label’s Lines property, which is the maximum number of lines the label can have, to 0 in the Attributes inspector.

When you’re done, it’ll look like this:

Add bio label to AuteurTableViewCell

Important: Setting the number of lines is very important for dynamically sized cells. A label with its number of lines set to 0 will grow based on how much text it shows. A label with its number of lines set to any other number will truncate the text once it’s out of available lines.

Next, you need to connect the bioLabel outlet of AuteurTableViewCell to the label on the cell. One quick way to do this is to right-click AuteurCell in the Document Outline. Then, click-drag from the outlet’s empty circle to to the corresponding Bio label:

Connect bioLabel outlet to the label on the AuteurTableViewCell

Adding Constraints to Each Subview

One way to get self-sizing views working with Auto Layout on a UITableViewCell is to pin the all edges of the subview. That means that each subview will have leading, top, trailing and bottom constraints. The intrinsic height of the subviews will then dictate the height of each cell. You’ll do this next.

Note: If you’re not familiar with Auto Layout, or if you’d like a refresher to understand how to set up Auto Layout constraints, look at our Auto Layout in iOS tutorial.

Select bioLabel. Click the Add New Constraints button at the bottom of your storyboard. In this menu, select the four red lines near the top of the dialog. Next, change the leading and trailing values to 8 and click Add 4 Constraints. It’ll look like this:

Add constraints to the bio label

This ensures that, no matter how big or small the cell may be, the bio label is always:

  • 0 points from the top and bottom margins.
  • 8 points from the leading and trailing margins.

Now that you’ve connected bioLabel to the top and bottom margins by 0 points, Auto Layout can determine the height of the cell!

Awesome, you’ve set up AuteurTableViewCell! Build and run. Now, you’ll see:

After running with Auto Layout, each row of the Auteurs list shows the name on top of the word Bio.

Yikes! That’s not right. Cut!

Before the cells can become dynamic, you need to write a bit of code. You’ll do that next.

Configuring the Table View

First, you need to configure the table view to use AuteurTableViewCell.

Open AuteurListViewController.swift. Replace tableView(_:cellForRowAt:) with the following:

func tableView(
  _ tableView: UITableView,
  cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
  // 1
  let cell = tableView.dequeueReusableCell(
    withIdentifier: "AuteurCell", for: indexPath)
  // 2
  if let cell = cell as? AuteurTableViewCell {
    let auteur = auteurs[indexPath.row]
    cell.bioLabel.text = auteur.bio
  }
  return cell
}

The code above is pretty straightforward. In it, you:

  1. Dequeue an AuteurCell. This is the identifier from the storyboard.
  2. Set the bio text from the current row of the auteurs array and return the cell.

Still in AuteurListViewController.swift, add these two lines of code at the end of viewDidLoad():

tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 600

Setting the row height to UITableView.automaticDimension tells the table view to use the Auto Layout constraints and the contents of its cells to determine each cell’s height.

For the table view to do this, you must also provide estimatedRowHeight. In this case, 600 is a roughly estimated value that works well in this particular instance.

For your own projects, you’ll need to determine a good estimate of the average height. Any value will work, but the accuracy of the estimate affects the efficiency of the layout.

Build and run. You’ll now see each auteur’s full bio.

First working auto layout for auteur cell showing the full bio text for the auteur

That looks better! But wouldn’t a moody dark background make this app even more dramatic?

Adding a Background

Open Main.storyboard. In the table view of Auteurs Scene, select AuteurCell. In the Attributes inspector, click the Background drop-down menu:

Select the AuteurCell background color property

Select AuteursBackground for a nice black-ish background — pure black is not cool enough for auteurs. :]

In the same scene, set the same background color for Table View and View.

Now, select Bio Label. Set the text color to System Gray 4 Color so the text will stand out against the new background color.

Set bio label color to system gray 4

You want the navigation header to have the same background color, so open AppDelegate.swift and paste this code after var window: UIWindow?:

func applicationDidFinishLaunching(_ application: UIApplication) {
  let appearance = UINavigationBarAppearance()
  appearance.configureWithOpaqueBackground()
  appearance.backgroundColor = UIColor(named: "AuteursBackground")
  appearance.titleTextAttributes = [
    NSAttributedString.Key.foregroundColor: UIColor.systemGray2
  ]
  appearance.largeTitleTextAttributes = [
    NSAttributedString.Key.foregroundColor: UIColor.systemGray2
  ]
  UINavigationBar.appearance().standardAppearance = appearance
  UINavigationBar.appearance().compactAppearance = appearance
  UINavigationBar.appearance().scrollEdgeAppearance = appearance
}

The code above defines the appearance of UINavigationController throughout your app. It will give it an opaque background using AuteursBackground as the background color and System Gray 2 as the text color.

Build and run. It’ll look like this:

The Auteurs list and navigation bar now have the same dark color.

Such drama!

Adding More User Interfaces

It’s nice to read the entire bio of each artist, but there’s more data to show: the artist’s name, image and the source of the information. These additional pieces of data will make the app look much better.

Your next goal is to add an image view to AuteurTableViewCell, a label for the auteur’s name and a label for the source of the photo.

Open AuteurTableViewCell.swift. Add the following properties:

@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var sourceLabel: UILabel!
@IBOutlet weak var auteurImageView: UIImageView!

These UI elements will display the auteur’s name, information source and image.

Open Main.storyboard. In bio label, remove the constraints you added earlier.

You can select and remove all four constraints in the Document Outline.

Delete all of the bio label constraints

Before adding more elements, you’ll want some elbow room. Select AuteurCell from the Document Outline. Then, in the Size inspector, change the row height to 450.

Set the AuteurCell row height to 450

Adding Views to the Cell

Now, you’ll fill up the space you just added. From the Library, drag:

  • An Image View into the cell.
  • A Label into the cell. Set the text to Name and color to System Gray 4.
  • Another Label into the cell. Set the text to Source and font size to System 13.0 and color to System Gray 4.

Next, connect the outlets for the new image view and labels:

Connect the AuteurCell elements to their AuteurTableViewCell outlets.

Adding New Constraints on a Stack View

Instead of creating constraints for every view, you’re going to use a stack view to limit the number of constraints. Embed the labels and image view from AuteurTableViewCell in a stack view.

In the Document Outline, order the elements from top to bottom as follows within the stack view:

  1. Image View
  2. Name Label
  3. Bio Label
  4. Source Label

Stack view with elements ordered

Select the stack view. In the Attributes inspector, set the alignment to Fill and spacing to 8.

Stack view settings

Add the following constraints to the stack view:

  • 0 points from the top and bottom margins.
  • 8 points from the leading and trailing margins.

Add the following constraints to the image view:

  • 1:1 width-to-height aspect ratio.

Aspect ratio width to height one to one

Just like that, you’ve pinned the edges of the stack view its superview’s edges. Plus, you’ve ensured a 1:1 width-to-height aspect ratio with the image view.

However, the labels have equal vertical content hugging priority and it’s ambiguous to Auto Layout to prioritize which label should shrink or expand over the other. You’ll inform Auto Layout about this next.

Setting the Hugging Priorities

You’ll set the hugging vertical priorities for the name label and bio label.

Select Name Label and, in the Size inspector, scroll down until you see Content Hugging Priority.

By default, horizontal and vertical content hugging priorities are 251. Change the name label’s vertical content hugging priority to 253.

Setting name label vertical content hugging priority

Now, select the Bio Label and set its Vertical priority to 252.

Note: Wait, what’s with all the hugging? Is this a romance picture?

Not exactly. Setting a higher priority on content hugging means the view will resist growing larger than its intrinsic size. You told the storyboard to make your cell 450 points tall, which is larger than the intrinsic size of your views. Setting a vertical content hugging priority tells Xcode which view to expand if it needs to fill the space.

You have the layout of your table view cell without all that much cognitive efforts. However, your UI wouldn’t be complete without populating the new views.

Populating the New Views

Now, you’ll populate the views. Open AuteurTableViewCell.swift. Add the following method:

func configure(
  name: String,
  bio: String,
  sourceText: String,
  imageName: String
) -> AuteurTableViewCell {
  // 1
  nameLabel.text = name
  bioLabel.text = bio
  sourceLabel.text = sourceText
  auteurImageView.image = UIImage(named: imageName)
  // 2
  nameLabel.textColor = .systemGray2
  bioLabel.textColor = .systemGray3
  sourceLabel.textColor = .systemGray3
  // 3
  sourceLabel.font = UIFont.italicSystemFont(
    ofSize: sourceLabel.font.pointSize)
  nameLabel.textAlignment = .center
  // 4
  selectionStyle = .none
  return self
}

The code above is fairly self-explanatory. You’re simply setting the:

  1. Contents of each of the views.
  2. Label text colors to different system colors that adapt to light and dark mode.
  3. Source label to use an italicized system font.
  4. Cell selection style to none.

Open AuteurListViewController.swift. Replace tableView(_:cellForRowAt:) with:

func tableView(
  _ tableView: UITableView,
  cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(
    withIdentifier: "AuteurCell",
    for: indexPath
  ) as? AuteurTableViewCell ?? AuteurTableViewCell(
    style: .default, reuseIdentifier: "AuteurCell")
  let auteur = auteurs[indexPath.row]
  return cell.configure(
    name: auteur.name,
    bio: auteur.bio,
    sourceText: auteur.source,
    imageName: auteur.image)
}

This uses configure(name:bio:sourceText:imageName:) to configure the cell for display.

Build and run.

Auteurs list now has images

Not bad, but you can take it to the next level by adding expanding cells to reveal more information about films from an auteur. Your client will love this!

Expanding Cells

Since Auto Layout constraints drive your cell heights and the content of each interface element, expanding the cell is 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,
      var film = selectedAuteur?.films[indexPath.row]
    else {
      return
    }
    // 2
    film.isExpanded.toggle()
    selectedAuteur?.films[indexPath.row] = film
    // 3
    tableView.beginUpdates()
    cell.configure(
      title: film.title,
      plot: film.plot,
      isExpanded: film.isExpanded,
      poster: film.poster)
    tableView.endUpdates()
    // 4
    tableView.scrollToRow(at: indexPath, at: .top, animated: true)
  }
}

Here’s what’s happening:

  1. You ask the tableView for a reference to the cell at the selected indexPath and then get the corresponding Film.
  2. Toggle the isExpanded state of the Film object and add it back into the array — which you need to do because structs are value types.
  3. Next, you tell the table view you’re going to make updates. You reconfigure the cell with the new value for film.isExpanded, then tell the table view the updates are complete. This causes the changes to animate.
  4. Finally, tell the table view to scroll to the selected row in an animated fashion.

Now, open FilmTableViewCell.swift. In configure(title:plot:isExpanded:poster:), paste the following code at the end of the method:

moreInfoTextView.text = isExpanded ? plot : Self.moreInfoText
moreInfoTextView.textAlignment = isExpanded ? .left : .center
moreInfoTextView.textColor = isExpanded ? .systemGray3 : .systemRed

The code above will reconfigure the cell text, alignment and color based on the value of isExpanded.

Build and run.

When you tap a film cell, you’ll see it expands to accommodate the full text.

Select an auteur and tap the films. You’ll see some very smooth cell expansion, revealing information about each film.

Film cell expands and collapses with smooth animation

When you tap a film cell, you’ll see it expands to accommodate the full text. Next, you’ll learn to integrate Dynamic Type in your app to make your app more accessible to a wider audience.

Implementing Dynamic Type

You’ve shown your progress to your client, and they love it! But they have one final request: They want the app to support the Larger Text Accessibility feature. The app needs to adjust to the customer’s preferred reading size.

Introduced in iOS 7, Dynamic Type makes this task easy. It gives developers the ability to specify different text styles for different blocks of text, like Headline or Body, and have that text adjust automatically when the user changes the preferred size in their Device Settings.

Open Main.storyboard. Select the Auteurs Scene. Select the Name Label.

In the Attributes inspector, complete the following steps:

  1. Click the T button from the Font settings.
  2. Select Headline under the Text Styles.
  3. Enable the Automatically Adjusts Font option.
  4. Set the Lines to 0.

You’ve allowed the name label’s text to adjust its font size using a font that supports Dynamic Type, enabling the setting to grow/shrink the font and wrap the text to the next line if it consumes all the horizontal space on a line.

Do the same with the Bio Label, but choose Body under Text Styles instead of Headline.

The labels in the Auteur Detail View Controller Scene have already been adjusted in the starter project to reduce repetitive tasks.

That’s all you need to do to make the app more accessible. Build and run.

Enter the home screen your simulator/device.

Open the Settings app. Do the following:

  1. Tap Accessibility ▸ Display & Text Size ▸ Larger Text.
  2. Enable Larger Accessibility Sizes.
  3. Drag the slider to the right.

You’ve increased the text size to a large setting.

Open the Auteurs app. Your text will now appear larger. Thanks to your work on implementing dynamically sized cells, the table view still looks great:

Enable large text in accessibility settings

Congratulations on completing this tutorial on self-sizing table view cells!

Where to Go From Here?

Download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.

Table views are perhaps the most fundamental of structured data views in iOS. As your apps get more complex, you’re likely to use all sorts of custom table view cell layouts.

For more about Auto Layout, check out our book Auto Layout by Tutorials.

We hope that you enjoyed this tutorial. If you have any questions or comments, or would like to show off your own self-sizing layouts, join the forum discussion below!

Average Rating

5/5

Add a rating for this content

5 ratings

More like this

Contributors

Comments