Stack View Baseline Alignment Issue

A horizontal stack view can align text based views on the first or last baseline of the text. Unfortunately the stack view ends up with an incorrect height when the first text view is not the view that extends furthest above/below the baseline. Adding some extra constraints works around the problem.

Baseline Alignment of Text

Here is what I expect to happen. I have a horizontal stack view with three text labels. The second text label uses a font that is twice the size of the other labels. There is a small amount of spacing between the views and I have set the alignment to .firstBaseline:

let stackView = UIStackView(arrangedSubviews: [label1, label2, label3])
stackView.spacing = 8.0
stackView.alignment = .firstBaseline

I centered the stack view below the top layout guide of a view controller. I also added a yellow background view to the stack view so we can see its frame. This is how I would expect it to look:

Expected Result

The three text labels are baseline aligned with the stack view height set by the height of the second (tallest above baseline) text label. The text labels should be within the bounds of the stack view.

Observed Result

The text labels align on their first baselines but the stack view pins its top edge to the top of the first (and smaller) text label. This causes the taller second text label to extend above the bounds of the stack view and in this case to end up under the navigation bar.

Observed Result

It works fine when the first label uses the largest font (or the labels are all the same font size):

Large first label

Missing Constraints

Looking at the configuration of the stack view with the view debugger it would appear to be missing constraints from the top of the stack view to the top of the 2nd and 3rd labels. The first text label has a constraint with the top of the stack view as follows:

UISV-canvas-connection: stackView.top <= label.top @ 1000

This constraint makes sure the stack view top is never over the top of the label. This constraint is missing for the other two labels. A workaround is to add this constraint to each label:

NSLayoutConstraint.activate([
  stackView.topAnchor.constraint(lessThanOrEqualTo: label2.topAnchor),
  stackView.topAnchor.constraint(lessThanOrEqualTo: label3.topAnchor)
  ])

Since this is a less than or equal to constraint the top of the stack view ends up positioned equal to the top of the label that extends the highest above the baseline.

If you are using a last baseline alignment replace topAnchor with bottomAnchor and use a greater than or equal to relation:

stackView.alignment = .lastBaseline
NSLayoutConstraint.activate([
  stackView.bottomAnchor.constraint(greaterThanOrEqualTo: label2.bottomAnchor),
  stackView.bottomAnchor.constraint(greaterThanOrEqualTo: label3.bottomAnchor)
  ])

Bug Report

Unless I am missing something I assume this is a bug. I have posted a sample Xcode project reproducing the problem and bug report on Open Radar if you want to duplicate:

Never miss a post!

iOS Size Classes Cheat Sheet

Subscribe and get my free iOS Size Classes Cheat Sheet

Success! Now check your email to confirm your subscription and download your free guide to iOS Size Classes.

There was an error submitting your subscription. Please try again.

Unsubscribe at any time.
No time to watch WWDC videos?

Sign up to get my iOS posts direct to your inbox and I will send you a free PDF of my iOS Size Classes Cheat Sheet.

OK! Check your inbox (or spam folder) for an email to confirm your details and download your free guide to iOS Size Classes.

There was an error submitting your subscription. Please try again.

Unsubscribe at any time.
Archives Categories