UIStackView is a great showcase of Auto Layout. It’s built using constraints which makes building an open source replacement easier. If you’re not in business of building one this article will give you a better understanding of how UIStackView interacts with Auto Layout.

The plan is simple. I’m going to create an instance of UIStackView, try different configurations, take screenshots, and print all of the constraints that affect the stack view layout. I’m going to discuss each of those configurations a bit. After that implementing a replacement of UIStackView should be as simple as translating those constraints into code. In fact I’ve already done that in a library named Arranged (which I’ve built more than 10 months ago, this follow-up post is a bit late).

Prerequisites:

Introduction

The UIStackView is an abstraction on top of Auto Layout for laying out a collection of views in either a column or a row.

The stack view manages the layout of all the views in its arrangedSubviews property. These views are arranged along the stack view’s axis, based on their order in the arrangedSubviews array. The exact layout varies depending on the stack view’s axis, distribution, alignment, spacing, and other properties.

In most examples I’m going to use a single UIStackView with 3 arranged views with a predefined intrinsic content sizes:

Intrinsic Content Size
 Subview0.width == 160 Hug:250 CompressionResistance:750
 Subview0.height == 200 Hug:250 CompressionResistance:750
 Subview1.width == 80 Hug:250 CompressionResistance:750
 Subview1.height == 100 Hug:250 CompressionResistance:750
 Subview2.width == 40 Hug:250 CompressionResistance:750
 Subview2.height == 50 Hug:250 CompressionResistance:750

Most of the configurations are going to have a .horizontal axis. If you’d like to jump to some specific configurations use a contents table below.

Contents

Distribution

Alignment

Other

UIStackView Distribution

The distribution determines how the stack view lays out its arranged views along its axis. Let’s start with a default UIStackView configuration, which is also one of the easiest to implement:

1.1 Distribution: .fill

A layout where the stack view resizes its arranged views so that they fill the available space along the stack view’s axis. When the arranged views do not fit within the stack view, it shrinks the views according to their compression resistance priority. If the arranged views do not fill the stack view, it stretches the views according to their hugging priority.

Constraints

Let’s print a full list of constraints inside the stack view using this code:

constraints(for: stackView).forEach { print($0) }

func constraints(for view: UIView) -> [NSLayoutConstraint] {
    var constraints = [NSLayoutConstraint]()
    constraints.append(contentsOf: item.constraints)
    for subview in item.subviews {
        constraints.append(contentsOf: subview.constraints)
    }
    return constraints
}

Each constraint gets printed in a full format like this:

<NSLayoutConstraint:0x6100000922f0 'UISV-canvas-connection'
   UIStackView:0x7f892be06190.leading == Subview0.leading
   (active, names: content-view-1:0x7f892be0bb70 )>

As you can see stack view sets a specific identifier based on the role that the constraint plays in the layout. Let’s convert the remaining constraints to a more concise format:

UISV-alignment:
 Subview0.top == Subview1.top
 Subview0.top == Subview2.top
 Subview0.bottom == Subview1.bottom
 Subview0.bottom == Subview2.bottom

UISV-canvas-connection:
 StackView.leading == Subview0.leading
 Subview2.trailing == StackView.trailing
 StackView.top == Subview0.top
 Subview0.bottom == StackView.bottom

UISV-spacing:
 H:[Subview0]-(0)-[Subview1]
 H:[Subview1]-(0)-[Subview2]

Constraint Categories

The fist two categories of the constraints ensure that the subviews fill the stack view both horizontally (distribution .fill) and vertically (alignment .fill):

  • UISV-alignment constraints align subviews horizontally by making them have equal .bottom and equal .top layout attributes (NSLayoutAttribute). Notice that the stack view pins all the subviews to the first one.
  • UISV-canvas-connection constraints connect subviews to the ‘canvas’ which is a stack view itself. The first and the last subview gets pinned to the .leading and .trailing layout attributes of the stack view respectively. The first subview also gets pinned to the .top and .bottom attributes.

UISV-spacing constraints simply add the same spacing between each of the subsequent subviews. The spacing is defined by the value of the stack view spacing property.

Intrinsic Content Size

As you probably noticed we haven’t defined the stack’s size along neither of its axis. In this case, the stack view’s size grows freely in both dimensions, based on the its arranged views. So the other remaining constraints that affect the stack view layout are intrinsic content size constraints which I’ve listed earlier. The stack view takes the height of the Subview0 which is the highest with Subview0.height == 200 Hug:250 CompressionResistance:750. The width of the stack view is equal to the combined width of all subviews + all spacings.

In the result we get a very simple but very useful layout. In terms of constraints this configuration is the simplest one to implement.

1.2 Distribution: .fillEqually

A layout where the stack view resizes its arranged views so that they fill the available space along the stack view’s axis. The views are resized so that they are all the same size along the stack view’s axis.

UISV-fill-equally:
 Subview1.width == Subview0.width
 Subview2.width == Subview0.width

UISV-alignment:
 Subview0.bottom == Subview1.bottom
 Subview0.bottom == Subview2.bottom
 Subview0.top == Subview1.top
 Subview0.top == Subview2.top

UISV-canvas-connection:
 StackView.leading == Subview0.leading
 Subview2.trailing == StackView.trailing
 StackView.top == Subview0.top
 Subview0.bottom == StackView.bottom

UISV-spacing:
 H:[Subview0]-(0)-[Subview1]
 H:[Subview1]-(0)-[Subview2]

This distribution is almost the same as fill distribution. The only different is the new UISV-fill-equally set of constraints. It forces all of the arranged views to have the same width (or height for vertical UIStackView).

1.3 Distribution: .fillProportionally

A layout where the stack view resizes its arranged views so that they fill the available space along the stack view’s axis. Views are resized proportionally based on their intrinsic content size along the stack view’s axis.

UISV-fill-proportionally:
 Subview0.width == 0.571429*StackView.width priority:999
 Subview1.width == 0.285714*StackView.width priority:998
 Subview2.width == 0.142857*StackView.width priority:997

Hardcoded Width (Custom Constraint):
 StackView.width == 200

UISV-alignment:
 Subview0.bottom == Subview1.bottom
 Subview0.bottom == Subview2.bottom
 Subview0.top == Subview1.top
 Subview0.top == Subview2.top

UISV-canvas-connection:
 StackView.leading == Subview0.leading
 Subview2.trailing == StackView.trailing
 StackView.top == Subview0.top
 Subview0.bottom == StackView.bottom

UISV-spacing:
 H:[Subview0]-(0)-[Subview1]
 H:[Subview1]-(0)-[Subview2]

This one is also similar to the fill distribution, but it requires a new UISV-fill-proportionally category of constraints:

UISV-fill-proportionally:
 Subview0.width == 0.571429*StackView.width priority:999
 Subview1.width == 0.285714*StackView.width priority:998
 Subview2.width == 0.142857*StackView.width priority:997

The multipliers (0.571429, 0.285714, etc) are calculated as a proportion of an arranged view intrinsic content width to the combined intrinsic content width of all visible arranged views + size of all spacings.

At first this layout seems relatively simple to implement. But there are some potential issues. What happens when not all (or none) of the arranged views have an intrinsic content size? What happens when the intrinsic content size of some of the arranged views changes? Well, as to the last question, UIStackView uses a neat private method _intrinsicContentSizeInvalidatedForChildView in which it recalculate constraints’ multipliers. Unfortunately we can’t do that, and there doesn’t seem to be any other way to implement this.

Also notice that the priority of the constraints is higher then both content hugging and compression resistance priorities which both effectively get ignored (unless you change those).

I’m not sure why each constraint has a different priority (999, 998, etc). If you have an idea please leave a comment below.

1.4 Distribution: .equalSpacing

A layout where the stack view positions its arranged views so that they fill the available space along the stack view’s axis. When the arranged views do not fill the stack view, it pads the spacing between the views evenly. If the arranged views do not fit within the stack view, it shrinks the views according to their compression resistance priority.

UISV-distributing-edge:
 H:[Subview0]-(0)-[_UIOLAGapGuide:0x6080001dbc60]
 H:[_UIOLAGapGuide:0x6080001dbc60]-(0)-[Subview1.leading]
 H:[Subview1]-(0)-[_UIOLAGapGuide:0x6080001db8a0]
 H:[_UIOLAGapGuide:0x6080001db8a0]-(0)-[Subview2.leading]

UISV-fill-equally:
 _UIOLAGapGuide:0x6080001db8a0.width == _UIOLAGapGuide:0x6080001dbc60.width

UISV-spacing:
 H:[Subview0]-(>=10)-[Subview1]
 H:[Subview1]-(>=10)-[Subview2]

UISV-alignment:
 Subview0.bottom == Subview1.bottom
 Subview0.bottom == Subview2.bottom
 Subview0.top == Subview1.top
 Subview0.top == Subview2.top

UISV-canvas-connection:
 StackView.leading == Subview0.leading
 Subview2.trailing == StackView.trailing
 StackView.top == Subview0.top
 Subview0.bottom == StackView.bottom

This configuration requires extra spacers (_UIOLAGapGuide) between subsequent subviews. Spacers are pinned to respected subviews by UISV-distributing-edge constraints. All the spacers have the same size thanks to UISV-fill-equally constraints. The UISV-spacing constraints are a bit different from previous distributions too - they now use NSLayoutRelation.greaterThanOrEqual rather than NSLayoutRelation.equal.

1.5 Distribution: .equalCentering

A layout that attempts to position the arranged views so that they have an equal center-to-center spacing along the stack view’s axis, while maintaining the spacing property’s distance between views. If the arranged views do not fit within the stack view, it shrinks the spacing until it reaches the minimum spacing defined by its spacing property. If the views still do not fit, the stack view shrinks the arranged views according to their compression resistance priority.

UISV-distributing-edge:
 _UIOLAGapGuide:0x6000001dc5c0.leading == Subview0.centerX
 _UIOLAGapGuide:0x6000001dc5c0.trailing == Subview1.centerX
 _UIOLAGapGuide:0x6000001dc2f0.leading == Subview1.centerX
 _UIOLAGapGuide:0x6000001dc2f0.trailing == Subview2.centerX

UISV-fill-equally:
 _UIOLAGapGuide:0x6000001dc2f0.width ==_UIOLAGapGuide:0x6000001dc5c0.width priority:149

UISV-spacing:
 H:[Subview0]-(>=0)-[Subview1]
 H:[Subview1]-(>=0)-[Subview2]

UISV-alignment:
 Subview0.bottom == Subview1.bottom
 Subview0.bottom == Subview2.bottom
 Subview0.top == Subview1.top
 Subview0.top == Subview2.top

UISV-canvas-connection:
 StackView.leading == Subview0.leading
 Subview2.trailing == StackView.trailing
 StackView.top == Subview0.top
 Subview0.bottom == StackView.bottom

Equal centering distribution is similar to the .EqualSpacing because it also uses spacers (_UIOLAGapGuide) between subsequent views. The main difference is how those spacers are used. Instead of pinning spacers to the respective .leading and .trailing layout attributes of the views they are now pinned to the .centerX.

The other difference is that UISV-fill-equally constraints now have a low priority of 149, which is lower than the default compression resistance, which ensures that:

If the arranged views do not fit within the stack view, it shrinks the spacing until it reaches the minimum spacing defined by its spacing property. If the views still do not fit, the stack view shrinks the arranged views according to their compression resistance priority.

The minimum spacing is again achieved by UISV-spacing constraints which all have a required priority.

UIStackView Alignment

2.1 Alignment: .fill

A layout where the stack view resizes its arranged views so that they fill the available space perpendicular to the stack view’s axis.

UISV-alignment:
 Subview0.bottom == Subview1.bottom
 Subview0.bottom == Subview2.bottom
 Subview0.top == Subview1.top
 Subview0.top == Subview2.top

UISV-canvas-connection:
 StackView.leading == Subview0.leading
 Subview2.trailing == StackView.trailing
 StackView.top == Subview0.top
 Subview0.bottom == StackView.bottom

UISV-spacing:
 H:[Subview0]-(0)-[Subview1]
 H:[Subview1]-(0)-[Subview2]

This configuration is exactly the same as the very first one: fill distribution.

2.2 Alignment: .leading

A layout for vertical stacks where the stack view aligns the leading edge of its arranged views along its leading edge.

UISV-alignment:
 Subview0.top == Subview1.top
 Subview0.top == Subview2.top

UISV-spanning-boundary:
 _UILayoutSpacer.top == Subview0.top priority:999.5
 _UILayoutSpacer.bottom >= Subview0.bottom
 _UILayoutSpacer.top == Subview1.top priority:999.5
 _UILayoutSpacer.bottom >= Subview1.bottom
 _UILayoutSpacer.top == Subview2.top priority:999.5
 _UILayoutSpacer.bottom >= Subview2.bottom

UISV-spanning-fit:
 _UILayoutSpacer.height == 0 priority:51

UISV-canvas-connection:
 StackView.leading == Subview0.leading
 Subview2.trailing == StackView.trailing
 StackView.top == Subview0.top
 _UILayoutSpacer.bottom == StackView.bottom

UISV-ambiguity-suppression:
 Subview0.height == 0 priority:25
 Subview1.height == 0 priority:25
 Subview2.height == 0 priority:25

UISV-spacing:
 H:[Subview0]-(0)-[Subview1]
 H:[Subview1]-(0)-[Subview2]

That’s a lot of constraints compared to the previous configurations! Let’s figure out what’s going on here.

Stack views adds a new auxiliary _UILayoutSpacer view to which it pins all of the arranged views with UISV-spanning-boundary constraints. Notice that all .bottom constraints use .greaterThanOrEqual relation.

Spacer’s height is bounded by UISV-spanning-fit constraint to disambiguate its height. The height of the arranged views also gets disambiguated automatically by stack view for you (UISV-ambiguity-suppression constraints).

Interestingly vertical UISV-canvas-connection constraints pin different views to the canvas this time. The first arranged view gets pinned to the top, while the spacer (_UILayoutSpacer) gets pinned to the bottom.

Now why is that so complicated? Why is layout spacer necessary and is it necessary at all? Couldn’t we just pin all the arranged views to the stack view itself? I think that it might be excessive, but I might be missing something. If you have an idea why it’s implemented this way please share it in the comments.

2.3 Alignment: .center

A layout where the stack view aligns the center of its arranged views with its center along its axis.

UISV-alignment:
 Subview0.centerY == Subview1.centerY
 Subview0.centerY == Subview2.centerY

UISV-spanning-boundary:
 _UILayoutSpacer.bottom >= Subview0.bottom
 _UILayoutSpacer.bottom >= Subview1.bottom
 _UILayoutSpacer.bottom >= Subview2.bottom
 _UILayoutSpacer.top <= Subview0.top
 _UILayoutSpacer.top <= Subview1.top
 _UILayoutSpacer.top <= Subview2.top

UISV-spanning-fit:
 _UILayoutSpacer.height == 0 priority:51

UISV-canvas-connection:
 StackView.leading == Subview0.leading
 Subview2.trailing == StackView.trailing
 StackView.top == _UILayoutSpacer.top
 _UILayoutSpacer.bottom == StackView.bottom
 StackView.centerY == Subview0.centerY

UISV-ambiguity-suppression:
 Subview0.height == 0 priority:25
 Subview1.height == 0 priority:25
 Subview2.height == 0 priority:25

UISV-spacing:
 H:[Subview0]-(0)-[Subview1]
 H:[Subview1]-(0)-[Subview2]

This and the following alignment (.trailing) is very similar to a leading alignment so I’m not going to comment them that much. This particular configuration has an extra UISV-canvas-connection constraint that pins first arranged view to the .centerY of the stack view, and has slightly different UISV-spanning-boundary and UISV-alignment. But the idea is the same.

2.4 Alignment: .trailing

A layout for vertical stacks where the stack view aligns the trailing edge of its arranged views along its trailing edge.

UISV-alignment:
 Subview0.bottom == Subview1.bottom
 Subview0.bottom == Subview2.bottom

UISV-spanning-boundary:
 _UILayoutSpacer.top <= Subview0.top
 _UILayoutSpacer.top <= Subview1.top
 _UILayoutSpacer.top <= Subview2.top
 _UILayoutSpacer.bottom == Subview0.bottom priority:999.5
 _UILayoutSpacer.bottom == Subview1.bottom priority:999.5
 _UILayoutSpacer.bottom == Subview2.bottom priority:999.5

UISV-spanning-fit:
 _UILayoutSpacer.height == 0 priority:51

UISV-canvas-connection:
 StackView.leading == Subview0.leading
 Subview2.trailing == StackView.trailing
 StackView.top == _UILayoutSpacer.top
 Subview0.bottom == StackView.bottom

UISV-ambiguity-suppression:
 Subview0.height == 0 priority:25
 Subview1.height == 0 priority:25
 Subview2.height == 0 priority:25

UISV-spacing:
 H:[Subview0]-(0)-[Subview1]
 H:[Subview1]-(0)-[Subview2]

2.5 Alignment: .firstBaseline

A layout where the stack view aligns its arranged views based on their first baseline. This alignment is only valid for horizontal stacks.

UISV-alignment:
 Subview0.firstBaseline == Subview1.firstBaseline
 Subview0.firstBaseline == Subview2.firstBaseline

UISV-spanning-boundary:
 _UILayoutSpacer.top <= Subview0.top
 _UILayoutSpacer.bottom >= Subview0.bottom
 _UILayoutSpacer.top <= Subview1.top
 _UILayoutSpacer.bottom >= Subview1.bottom
 _UILayoutSpacer.top <= Subview2.top
 _UILayoutSpacer.bottom >= Subview2.bottom

UISV-spanning-fit:
 _UILayoutSpacer.height == 0 priority:51

UISV-canvas-fit:
 StackView.height == 0 priority:49

UISV-canvas-connection:
 StackView.leading == Subview0.leading
 Subview2.trailing == StackView.trailing
 StackView.top == _UILayoutSpacer.top
 _UILayoutSpacer.bottom == StackView.bottom

UISV-text-width-disambiguation:
 Subview0.width == 0.333333*StackView.width priority:760
 Subview1.width == 0.333333*StackView.width priority:760
 Subview2.width == 0.333333*StackView.width priority:760

UISV-ambiguity-suppression:
 Subview0.height == 0 priority:25
 Subview1.height == 0 priority:25
 Subview2.height == 0 priority:25

UISV-spacing:
 H:[Subview0]-(0)-[Subview1]
 H:[Subview1]-(0)-[Subview2]

The .firstBaseline and the .lastBaseline alignments only make sense for views like UILabel which has some content with actual baselines (like text).

This alignment is again very similar to the previous ones. It uses an auxilary spacer, it connects spacer to the stack view, etc. The only constraints that stick out are new UISV-text-width-disambiguation constraints. This is quite surprising. There is no trace of this behavior neither in the UIStackView documentation not in headers. You can see what those constraints do, but why and when does UIStackView add those constraints remains mystery to me. Those constraints are not implemented in Arranged for the above reasons.

2.6 Alignment: .lastBaseline

A layout where the stack view aligns its arranged views based on their last baseline. This alignment is only valid for horizontal stacks.

UISV-alignment:
 Subview0.lastBaseline == Subview1.lastBaseline
 Subview0.lastBaseline == Subview2.lastBaseline

UISV-spanning-boundary:
 _UILayoutSpacer.top <= Subview0.top
 _UILayoutSpacer.bottom >= Subview0.bottom
 _UILayoutSpacer.top <= Subview1.top
 _UILayoutSpacer.bottom >= Subview1.bottom
 _UILayoutSpacer.top <= Subview2.top
 _UILayoutSpacer.bottom >= Subview2.bottom

UISV-spanning-fit:
 _UILayoutSpacer.height == 0 priority:51

UISV-canvas-fit:
 StackView.height == 0 priority:49

UISV-canvas-connection:
 StackView.leading == Subview0.leading
 Subview2.trailing == StackView.trailing
 StackView.top == _UILayoutSpacer.top
 V:[Subview0]-(>=0)-|

UISV-text-width-disambiguation:
 Subview0.width == 0.333333*StackView.width priority:760
 Subview1.width == 0.333333*StackView.width priority:760
 Subview2.width == 0.333333*StackView.width priority:760

UISV-ambiguity-suppression:
 Subview0.height == 0 priority:25
 Subview1.height == 0 priority:25
 Subview2.height == 0 priority:25

UISV-spacing:
 H:[Subview0]-(0)-[Subview1]
 H:[Subview1]-(0)-[Subview2]

UIStackView Misc

3.1 Hiding Subviews

The stack view automatically updates its layout whenever views are added, removed or inserted into the arrangedSubviews array, or whenever one of the arranged views’s isHidden property changes.

UISV-hiding:
 Subview0.width == 0

UISV-spacing:
 H:[Subview0]-(0)-[Subview1]
 H:[Subview1]-(10)-[Subview2]

UISV-alignment:
 Subview0.bottom == Subview1.bottom
 Subview0.bottom == Subview2.bottom
 Subview0.top == Subview1.top
 Subview0.top == Subview2.top

UISV-canvas-connection:
 StackView.leading == Subview0.leading
 Subview2.trailing == StackView.trailing
 StackView.top == Subview0.top
 Subview0.bottom == StackView.bottom

This one seems easy at first but actually is quite tricky. Hiding subviews is quite simple, UIStackView just adds a single UISV-hiding constraint and recalculates the spacings. What’s more complicated is how UIStackView reacts to isHidden changes and how it perform animations.

The UIStackView promises to do everything automagically for you:

// Animates removing the first item in the stack.
UIView.animateWithDuration(0.25) { () -> Void in
    let firstView = stackView.arrangedSubviews[0]
    firstView.hidden = true
}

What happens here is UIStackView observes isHidden property of the arranged views, cancels its effect, updates constraints, and automatically calls layoutIfNeeded() at the end of the animation block. I’m a little concerned about what kind of trickery with private APIs is used by UIStackView to make this work. I made a couple of attempts to make this work but ultimately decided that this behavior was both confusing and impractical to implement. I went with a simple fully controlled API:

UIView.animateWithDuration(0.33) {
    stackView.setArrangedView(view, hidden: true)
    stackView.layoutIfNeeded()
}

This way some of the responsibilities gets delegated to the user of the library, but all the complexities of working with isHidden are gone.

3.2 Margins Relative Layout

A Boolean value that determines whether the stack view lays out its arranged views relative to its layout margins. If true, the stack view will layout its arranged views relative to its layout margins. If false, it lays out the arranged views relative to its bounds.

UISV-alignment:
 Subview0.bottom == Subview1.bottom
 Subview0.bottom == Subview2.bottom
 Subview0.top == Subview1.top
 Subview0.top == Subview2.top

UISV-canvas-connection:
 UIViewLayoutMarginsGuide.leading == Subview0.leading
 UIViewLayoutMarginsGuide.trailing == Subview2.trailing
 UIViewLayoutMarginsGuide.top == Subview0.top
 UIViewLayoutMarginsGuide.bottom == Subview0.bottom

UIView-margin-guide-constraint:
 V:[UIViewLayoutMarginsGuide]-(8)-|
 H:|-(8)-[UIViewLayoutMarginsGuide]
 H:[UIViewLayoutMarginsGuide]-(8)-|
 |-(8)-[UIViewLayoutMarginsGuide]

UISV-spacing:
 H:[Subview0]-(0)-[Subview1]
 H:[Subview1]-(0)-[Subview2]

UISV-canvas-connection constraints now pin subviews to UIViewLayoutMarginsGuide instead of bounds.

3.3 Baseline Relative Layout

A Boolean value that determines whether the vertical spacing between views is measured from their baselines. If YES, the vertical space between views are measured from the last baseline of a text-based view, to the first baseline of the view below it. Top and bottom views are also positioned so that their closest baseline is the specified distance away from the stack view’s edge. This property is only used by vertical stack views.

UISV-spacing:
 Subview1.firstBaseline == Subview0.lastBaseline + 20
 Subview2.firstBaseline == Subview1.lastBaseline + 20

UISV-alignment:
 Subview0.leading == Subview1.leading
 Subview0.leading == Subview2.leading
 Subview0.trailing == Subview1.trailing
 Subview0.trailing == Subview2.trailing

UISV-canvas-connection:
 StackView.top == Subview0.top
 Subview2.bottom == StackView.bottom
 StackView.leading == Subview0.leading
 Subview0.trailing == StackView.trailing

The stack modifies UISV-spacing constraints by using .firstBaseline and .lastBaseline layout attributes.

3.4 Single Subview, Alignment: .Center

UISV-canvas-connection:
 StackView.leading == Subview0.leading
 H:[Subview0]-(0)-|
 StackView.top <= Subview0.top
 V:[Subview0]-(>=0)-|
 StackView.centerY == Subview0.centerY

UISV-canvas-fit:
 StackView.height == 0 priority:49

UISV-ambiguity-suppression:
 Subview0.height == 0 priority:25

Compared to center alignment with multiple subviews this configuration optimizes constraints by removing auxiliary spacer (_UILayoutSpacer) which is not necessary when there is just single arranged view.

Implementing UIStackView

I’m not going to dive into too much detail as far as actual the implementation goes. You’ve probably already noticed that most of the configurations have something in common. All of the constraints are split into well-defined categories and follow specific rules. The only thing that remains is to translate those categories and rules into code (actually it was a bit challenging given the number of options and different scenarios).

If you’d like to check out the actual code please take a look at Arranged sources. The entire implementation takes about 500 lines of code, most of which aren’t even constraint related.

Performance

UIStackView has a bunch of optimizations under the hood. One thing that it does for certain is updating only the constraints that need to be changed when some of stack view options change (like, say, distribution). While this feature might be welcomed when the layout changes dynamically, I’ve decided to avoid those optimizations to keep code as simple and reliable as possible. Even UIStackView itself has a number of scenarios in which it fails to update all of the constraints when configuraiton changes

Testing

Testing Arranged was relatively simple. All I had to do was test that Arranged.StackView creates a set of constraints equivalent to the reference (UIStackView) in all of those cases.