UIStackView: Distribution vs. Alignment
UIStackView simplifies the view layout by reducing the number of constraints
Introduction
UIStackView
is one of the most important and powerful UIKit
components introduced in iOS 9. It is so powerful and elegant that it really speeds up a lot of us pushing to drop support for iOS 8.
For people that work with Auto Layout a lot, you will know that it is not particularly fun and easy to create NSLayoutConstraint
s. Since each view on a 2D screen has four degrees of freedom, it means that, in general, we would have to create at least four constraints for each view.
So, back to the introduction of stack view, was it that much of an innovation?
Not really, since Android’s equivalent LinearLay
out was introduced at the beginning of the universe (a.k.a. API level 1). Not sure why it took Apple eight years to introduce this component.
But anyway, we are all very happy that it is finally available.
Even though UIStackView
simplified the view layout a lot by reducing the number of constraints that we would have to implement otherwise, it can still get pretty complicated and somewhat confusing.
There are four main properties for a stack view:
The first one and the last one are relatively straightforward. We use axis
to define the orientation of the stack view, which can either be vertical
or horizontal
. (If a stack is turned sideways, it should no longer be called a stack, or should it 🤔?) For the last one, spacing
, as implied by the name, it specifies the spacing between views inside the stack view. How about the remaining two: distribution
and alignment
?
Although UIStackView
has become so popular and so commonly used in so many applications, many people may not understand it fully and may not be using it right.
That’s what we will focus on today. We will compare various distribution
and alignment
settings and see how they differ from one another.
Preparation
Before we get started, we need to create a few dummy views to put into our stack view. Here we go:
let redDummy = DummyBoxView(width: 40, height: 40, color: .red)
let greenDummy = DummyBoxView(width: 30, height: 80, color: .green)
let blueDummy = DummyBoxView(width: 80, height: 30, color: .blue)
The implementation of DummyBoxView
is very simple. There is a dashed border, in the middle, there is a label indicating the relative width and height.
As mentioned earlier, there are two axes
(orientations) for a stack view: horizontal
or vertical
. But for simplicity, we will only focus on the horizontal stack view for now.
For a vertical stack view, we just need to turn our head by 90 degrees, right?
Distribution
The possible configurations for distribution
are:
fill
fillEqually
fillProportionally
equalSpacing
equalCentering
Let’s just look at what they look like:
The upper two graphs are equalCentering
(left) and equalSpacing
(right). Their differences are not very obvious, especially when the sizes for the subviews are similar.
For equalCentering
, the subviews are placed so that center-to-center spacing between views is the same. For equalSpacing
, the spacings between views are the same.
The lower three graphs are fillEqually
(left), fillProportionally
(middle), fill
(right).
Notice that for these three settings, the subviews are all resized in some way to fill the stack view. fillEqually
and fillProportionally
should be pretty clear according to their names. fillEqually
means that all the subviews will be resized so that they are all the same width (for horizontal stack view). fillProportionally
on the other hand, will resize the subviews proportionally based on their size.
But, what happens with just fill
? Why is our red box so much wider?
With fill
, the stack view will choose one subview to resize.
In this case, the stack view will pick the subview with the lowest hugging priority. As we never explicitly set it, all the subviews have the same default hugging priority. When this happens, the stack view will choose the first one to stretch. In this case, the red one gets stretched, while green and blue get to keep their size.
One last thing that is important to know about these three fill*
settings is that the stack view uses the intrinsic content size of the subview for the calculations. Not the frame size! Not the constraint size!
Are you curious as to why all of them align to the bottom of the stack views? Let’s move on to the next section.
Alignment
The possible configurations for alignment
are:
fill
top
firstBaseline
center
bottom
lastBaseline
There are actually two more, leading
and trailing
, that we are going to ignore for now, as they are for a vertical stack view only.
The first three are pretty clear: they are bottom
(upper left), center
(upper middle), and top
(upper right). As you may guess, in the previous section, all the graphs are actually using the bottom
alignment.
The next one is fill
(lower left), where the stack view resizes all its views so that they fill the available space perpendicular to the stack view’s axis.
The last two are the most confusing: firstBaseline
(lower middle) and lastBaseline
(lower right). Not sure we can get much by just the names.
Let’s look at the official definitions:
firstBaseline
: A layout where the stack view aligns its arranged views based on their first baseline.lastBaseline
: A layout where the stack view aligns its arranged views based on their last baseline.
The definition is similar to when I ask you what a “matchbox” is and you tell me it is a “match box”. Let’s look at one more set of examples with more subviews.
firstBaseline
(left) is similar totop
where all the subviews are top-aligned. However, they are placed at the bottom of the stack view.lastBaseline
(right) is similar tobottom
where all the subviews are bottom-aligned. However, they are placed at the top of the stack view.
It is a bit hard for me to visualize how this would be useful. But apparently, it makes sense for aligning multi-line text views. We won’t go into more detail here. But if you are interested, this answer on stackoverflow explains it well.
How About the Vertical Stack View?
Actually, everything is still pretty much the same. The only little thing is that top
and bottom
should be replaced by leading
and trailing
. They are, under the hood, the same integers in the enum.
The End
Alright, that’s all for the stack view. Finally, can you guess what the settings are for this stack view?
- Hint 1: It is not bottom alignment.
- Hine 2: There is one invisible view.
As always, thanks for reading! Demo code available on GitHub with the complete set of examples.