Using Size Classes to Hide Stack View Contents

Last updated: Jun 12, 2020

I had an interesting question this week about stack views and size classes that I thought was worth sharing. Suppose you have a stack view containing three views and you want to hide one of them for certain size classes. For example with the vertical stack shown below what if I want to hide the bottom heart view when the device has a compact vertical size class?

Vertical Stack View

The writer was trying to do this using size classes in Interface Builder to control when to install the view. For example, to not have the view installed when the height is compact:

View installation

At first this looks like it will work. When you rotate the device to landscape the third view disappears as expected. Unfortunately when you rotate back to portrait the extra view shows up in the wrong spot:

Bad layout

The problem is that the stack view does not expect you to install and remove views in this way. It expects you to use its API (addArrangedSubview and removeArrangedSubview) so that it can automatically update constraints for the views it is managing.

A Better Approach - Hide the View

Luckily there is a much easier way. A stack view will automatically adjust the layout when you hide or unhide one of its views. So instead of using size classes in Interface Builder create an outlet for the view to our view controller.

@IBOutlet var extraHeart: UIImageView!

Then in the view controller we can use willTransitionToTraitCollection to test for the compact height and hide or unhide the view.

override func willTransition(to newCollection: UITraitCollection,
    with coordinator: UIViewControllerTransitionCoordinator) {
  super.willTransition(to newCollection, with: coordinator)
  configureView(newCollection.verticalSizeClass)
}

The configureView method hides and unhides the view based on the vertical size class (the guard makes sure we do not try this before the system has set our outlet):

private func configureView(_ verticalSizeClass: UIUserInterfaceSizeClass) {
  guard extraHeart != nil else {
    return
  }
  extraHeart.hidden = (verticalSizeClass == .compact)
}

The traitCollection property of the view controller gives us the size class to configure the view when we first load:

override func viewDidLoad() {
  super.viewDidLoad()
  configureView(traitCollection.verticalSizeClass)
}

That’s all you need to hide the view for compact heights:

Compact height

The updated sample code is in my Code Examples GitHub repository.

Further Reading