Skip to the content.

UIStackView hides the tediousness and complexity of the Auto Layout. It cuts the number of constraints needed for Auto Layout, and thus makes it effortlesss to hide some of its arragedSubviews or alter their sizes at runtime. This lab unveils UIStackView’s flexibility by building an autosizing UITableViewCell.

1. Overview

In this lab, we will build a TechCrunch table view to show the catalog of its news. Simple as it looks, the building process manifests how UIStackView can auto sizes the its height when the content size needs to grow, and how UIStackView takes care of the layout when certain arrangedSubviews are hidden.

Drawing

What you will learn

:checkered_flag: Automatically sizing the arragedSubviews based on their intrinsicContentSize
:checkered_flag: Automatically computing the height of UITableViewCell based on its content
:checkered_flag: Hiding the arrangedSubviews in UIStackView

What you’ll need

2. Get the sample code

:octocat: Clone the repository to your local computer, and switch to the bootcamp branch

git clone git@github.com:ripplearc/AutoSizeTableViewCell.git
git checkout bootcamp

Take a look at the project file structure: we will be mainly working on the view :file_folder:; model :file_folder: provides some mock data captured from TechCrunch for display; uiview :file_folder: contains protocol extensions to make the code in UITableViewController cleaner.

Build and run, it should show a blank UITableView.

Drawing

3. Basics about UIStackView

:green_apple: Apple’s official documentation provides an excellent interpretation of the design philosophy of UIStackView. UIStackView layouts its arrangedSubviews through its properties of axis, distribution, alignment, and spacing. Distribution decides how arrangedSubviews are laid out along the axis, and alignment decides how they are laid out perpendicular to the axis. Spacing are the exact or the minimum intervals betwween the subviews.

While there are five enumerations in distribution, fill is the default one and probably the most useful one. It instructs the interface builder to fill up the entire space along the axis with specified spacing. If there is too much space to be filled, then whichever has the lower Hugging Priority will be stretched. On the contrary, if there is too little space to accomodate all the arrangedSubviews, then whichever has the lower Compression Priority will be compressed.

If there is no width or height constraint on a horizontal or vertical UIStackView respectively, then UIStackView decides the total height of itself based on the sum of the IntrinsicContentSize of its arrangedSubviews, such as UILabel, UIButton or UIImageView.

:key:

The key to building an autosizing UITableViewCell is to allow the widgets with IntrinsicContentSize, whose content size could change, to decide their own height. To put that in a simple term, don’t set the height constraint on a widget whose height could change at runtime.

In terms of the UITableViewCell, the size of the width is fixed, and we let the UIStackView grows vertically based on the widgets’ IntrinsicContentSize, which frees the developer from calcuating the size of UILabel using sizeThatFits.

We do not have to worry that a UITableViewCell grows out of spaces along the vertical axis, therefore, it is unnecessary to set hugging and compression priority of the arrangedSubviews in a column. However, since the width is fixed, we will see an example where we can leverage hugging and compression.

Drawing

4. Layout using IntrinsicContentSize

In this section, we will build most of the UITableViewCell except the UIImageView. As you may notice, the majority of the arrangedSubviews are UILabels where we will have IntrinsicContentSize to decide their sizes.

:memo:

It is a good habit to name the components on the interface builder, then the error messages about missing or conflict constraints are more meaningful.

Drawing

This completes all the interface builder setup :dart:. Connect the widgets from the interface builder to their corresponding IBOutlets, go to ListTableViewCell.swift and populate the widgets with the model.

@IBOutlet weak var categoryLabel: UILabel!
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var timeLabel: UILabel!
@IBOutlet weak var authorLabel: UILabel!

func configure(with model:ListTableViewCellModel) {
    categoryLabel.text = model.category.uppercased()
    titleLabel.text = model.title
    authorLabel.text = model.author
    timeLabel.text = model.timeStamp
}

Go to ListViewController.swift and configure the tableView to auto sizing in the viewDidLoad. estimatedRowHeight is nothing but a guess.

tableView.estimatedRowHeight = 200
tableView.rowHeight = UITableViewAutomaticDimension
tableView.separatorColor = .clear

Run the app in both SE and 8 Plus :tada:

Drawing

  1. The UITableViewCell now accommodates Title Label with proper height given any number of lines.
  2. Author Label and Time Label are also sized with proper width while Author Label pushes Time Label towards the right edge when its content grows.

:octocat: If you have any difficulty in setting things up in the interface builder, commit all the changes and shift to the branch uilabel

git add --all
git commit -m 'setting up labels'
git checkout uilabel

5 UIImageView

We will place the UIImageView side by side with the Title Label, and let whichever has the greater height to decide the height the UITableViewCell. In addtion, we want bigger image when the screen size increases.

Drawing

:memo:

At this point, the interface builder should be free of errors. If you still have one, and you are certain you have followed all the steps, move the cursor to the bottom of the cell, and manually adjust the height of the cell until the error disappears.

Connect the IBOutlet and populate the Cover Image.

...
@IBOutlet weak var coverImage: UIImageView!
...
func configure(with model:ListTableViewCellModel) {
...
if let image = model.imageName {
    coverImage.isHidden = false
    coverImage.image = UIImage(named:image)
} else {
    coverImage.isHidden = true
}
...
}

If you are interested, you can also add a bookmark UIButton on the left upper corner, and a separator line. However those are not the focus of the lab.

Run the app in both SE and 8 Plus :tada:

Drawing

:octocat: You can also shift to the master branch.

git add --all
git commit -m `add uiimageview`
git checkout master

The Bitcoin one shows the case where the Cover Image decides the height of the cell, and the Automotive GM one shows that the Title Label can also push the height of the cell. The Title Label in the Automotive Cruise news occupies the entire cell width as the Cover Image is absent. The hiding behavior is taken care of by UIStackView.

6 Summary

We conclude the lab with an anatomy drawing of the layout, and highlights a few important settings. Other than the Dot View and Cover Image whose height are pre-defined by constant or aspect ratio, the rest of the widgets possess no height constraint and subject to their own IntrinsicContentSize.

Drawing

What we’ve learned

:white_check_mark: Use IntrinsicContentSize to decide the height of UILabel
:white_check_mark: Use Center Alignment to let the tallest widget to decide the height of the UIStackView
:white_check_mark: Let UIStackView to handle the hiding behavior of the widget
:white_check_mark: Automatic sizing of the UITableViewCell

References

  1. Apple Developer Documentation UIStackView It explains the a few common ways to pin the UIStackView relative to its superview.
  2. Self-sizing Table View Cells A very good example to build a gallery app where its UITableViewCell self sizes.
  3. UIStackView Demystified Illustration about the distribution, alignment, spacing and axis properties of UIStackView.