lukecsmith.co.uk

A VStack / HStack with equally sized children

Luke, 6 May 2020

One of the most basic things you can do with a UIStackView in UIKit is to set all its child views to have equal widths or heights. But theres a fundamental difference between layout in UIKit and the new era of layout in SwiftUI – no longer is it the case that a container view or superview can tell its children how they should lay themselves out. Instead – the children have the power to choose their own sizes, and the parent must take notice.

Because of this, creating equally sized children in SwiftUI is not a simple case of telling a VStack to make its children all the same size. Instead, the children must all indicate they have similar size requirements, and the parent can then work with them to arrange themselves so that they are ultimately equal in size. Its a totally different way of thinking and it takes some getting used to if like me, you’ve been working with UIKit and layout for close to ten years .. :).

So first, heres what I’m seeking to recreate with SwiftUI : this is a UIStackView, that contains two UILabels. The UIStackView has been set up with Distribution set to ‘Fill Equally’ which means both UILabels are the same size. The UIStackView has also been pinned to its own superview, which is the main screen view, so it fills the screen. The UILabels have their text centred to put it in the middle. Both UILabels fill equally sized top and bottom halves of the screen :

How it looks in a UIStackView

SwiftUI’s close relative of UIStackView (set to horizontal or vertical) is HStack or VStack. For a basic VStack setup, you can do the logical and just place two Text items within it, setting the background colour of each, to see how things stand without any configuration :

public var body: some View {
        VStack {
            Text("Top text centred in top box")
                .background(Color.green)
            Text("Bottom text centred in bottom box")
                .background(Color.blue)
        }
    }

And this gives you this result :

Normal VStack Layout

Both text items are taking up exactly as much space as they need (according to font size etc). Then the entire VStack is being centred in the content view, in the middle. But the VStack does not fill the space (which we achieve in UIKit by pinning the edges to the superview).

The answer comes in adding a frame property to each child view, that specifies that the child should expand to fill as much space as it can (with respect to other views). SwiftUI does this by requesting an infinite width or height for a views frame – see the new modifier on each text view in this snippet.

public var body: some View {
        VStack {
            Text("Top text centred in top box")
                .frame(minWidth: 0,
                       maxWidth: .infinity,
                       minHeight: 0,
                       maxHeight: .infinity)
                .background(Color.green)
            Text("Bottom text centred in bottom box")
                .frame(minWidth: 0,
                       maxWidth: .infinity,
                       minHeight: 0,
                       maxHeight: .infinity)
                .background(Color.blue)
        }.background(Color.yellow)
    }

And this gives exactly the desired result, making all views fill as much area as they can, while respecting that the other child views also need to do the same. This works for HStacks and VStacks too.

Finished Layout
Tagged with: