Skip to main content

Swift: Common Auto Layout scenarios for UIStackView


If you are using content with an intrinsic size (e.g. a collection of UIImageViews), then you are likely to want to allow a UIStackView to freely expand around the content. In order to do this I think there are only really a limited set of common scenarios, which can be very simply divided into two.

A Centred UIStackView

The first one is to pin it to the top layout guide and to centre on the X-axis:
// top constraint
let topConst = NSLayoutConstraint(item: stackView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem:topLayoutGuide, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
// centre constraint
let centerConst = NSLayoutConstraint(item: stackView, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem:view, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0)
        
NSLayoutConstraint.activateConstraints([topConst,centerConst])
And here's the alternative code for this (using Anchors):
let topConst =  stackView.topAnchor.constraintEqualToAnchor(topLayoutGuide.bottomAnchor)
let centerConst = stackView.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor)

NSLayoutConstraint.activateConstraints([topConst,centerConst])
Variations on this could be to pin the stack view to the bottom layout guide:
let bottomConst =  stackView.bottomAnchor.constraintEqualToAnchor(bottomLayoutGuide.topAnchor)
let centerConst = stackView.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor)

NSLayoutConstraint.activateConstraints([bottomConst,centerConst])
Or to simply centre in x and y directions:
let centerYConst =  stackView.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor)
let centerConst = stackView.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor)

NSLayoutConstraint.activateConstraints([centerYConst,centerConst])
I could write out the code for each of these variants in the old way as well but since a UIStackView is newly available in OS 9 the new anchor approach to setting constraints will always be be there for it. (The only proviso here is that playgrounds don't currently work well with the anchor approach.)

Pinned to the sides

The alternative scenario is for a UIStackView to be pinned by its side.
let topConst =  stackView.topAnchor.constraintEqualToAnchor(topLayoutGuide.bottomAnchor)
let leftConst = stackView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor)

NSLayoutConstraint.activateConstraints([topConst,leftConst])
As before we might vary this by centring on the Y axis:
let centerYConst =  stackView.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor)
let leftConst = stackView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor)

NSLayoutConstraint.activateConstraints([centerYConst,leftConst])
Pinning to the right side:
let centerYConst =  stackView.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor)
let rightConst = stackView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor)

NSLayoutConstraint.activateConstraints([centerYConst,rightConst])
Or connecting to the bottom of the view:
let bottomConst = stackView.bottomAnchor.constraintEqualToAnchor(bottomLayoutGuide.topAnchor)
let rightConst = stackView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor)

NSLayoutConstraint.activateConstraints([bottomConst,rightConst])

A final point or two

With the stack view size being set by the content, the bottom of the view won't be at a set position. However, along with accessing the bottomAnchor of the stack view itself, there are also properties that enable access to the baseline of the last view and the first view inside a stack.
stackView.viewForLastBaselineLayout.bottomAnchor
and employ it like so:
secondStackView.topAnchor.constraintEqualToAnchor(stackView.viewForLastBaselineLayout.bottomAnchor)
Above is the code for the baseline at the bottom of the stack, while the .viewForFirstBaselineLayout property, from which we can extract the .topAnchor, gives us information about the top view inside a stack view.

Another thing that you should also take into account is that when the content is determining the size of the stack view, if the size of the content exceeds the width or height of the stack view's superview then the stack view will similarly exceed the size of its superview. This means that alongside the distribution properties of the stack view, you might need to put in place strategies to make sure that content is contained within the boundaries of the screen. These strategies might include content scaling, changing a stack view axis from horizontal to vertical, and so on.

It's very easy to use a stack view that is pinned to every edge of its superview, because it stretches and squashes content to fit but if you want content to behave more intelligently then there's work to be done.

Conclusion

This has been a brief, note to self post. I've not presented code here for distribution and alignment properties of the stack views themselves, this will come in a later post. I've also not repeated information about writing Auto Layout code featured in yesterday's post. What I have done is to try and interpret Apple's outline of how a top-level UIStackView is to be positioned and to translate this into code. You should also read Apple's guidance in positioning stack views and the content within them.

Note: testing this with regular views that are happy to be be pulled and stretched out of shape you may well find frustrating, I recommend instead using image views or buttons to see the effect.


Endorse on Coderwall

Comments