Exploding Stacks

A common layout desire is to place views in the corner of a larger view, or of the device. This is particularly interesting because it’s more instructive to visit the methods that don’t work and explain why, before showing the best way to do it.

Our desired result is as follows:

view with images in each corner

We’ll concentrate first on the problem of putting the dragoon Image in the bottom-right, since once that’s solved, the others are easy.

Having read about flexible frames and infinite frames, stacks, and alignments, our first attempt might be to use an alignment of a z-axis stack, and place that in an infinite frame:

struct ContentView: View {
    var body: some View {
        // ⚠️ This is an example that does not work.
        ZStack(alignment: .bottomTrailing) {
            Image("dragoon")
        }
        .frame(minWidth: 0, maxWidth: .infinity,
               minHeight: 0, maxHeight: .infinity)
    }
}

But that means we skipped views have fixed sizes, where we learned that .frame creates a new view and positions the modified view inside it. And that’s what happens:

view with frame around stack

The Image has a fixed size, and the ZStack has only the minimum size necessary to contain its children. The frame around it has the full size and positions the ZStack inside it, centered because that’s the default of the frame.

So since we can’t use a .frame around the ZStack to change the size of it, what can we do?

We can put the infinite frame inside the ZStack, around the Image:

struct ContentView: View {
    var body: some View {
        // ⚠️ This is an example that does not work.
        ZStack(alignment: .bottomTrailing) {
            Image("dragoon")
                .frame(minWidth: 0, maxWidth: .infinity,
                       minHeight: 0, maxHeight: .infinity)
        }
    }
}

I refer to this trick as exploding the stack, the stack has the minimum size of its children, but its child is the frame around the Image and that causes it to be sized as large as it can be:

view with frame around image inside stack

The ZStack now fills the entire device, and the Image is still fixed in size within it, but we’re still having an issue with the Image being centered rather than aligned.

This might seem like a surprise since the ZStack has a specified alignment of .bottomTrailing, but what we’ve missed here is that the child of stack is now the frame causing it to explode out; the Image is being positioned by the .frame and not the ZStack.

What we need to do is move the alignment to the .frame:

struct ContentView: View {
    var body: some View {
        ZStack {
            Image("dragoon")
                .frame(minWidth: 0, maxWidth: .infinity,
                       minHeight: 0, maxHeight: .infinity,
                       alignment: .bottomTrailing)
        }
    }
}

Since the ZStack has just one child, its alignment isn’t important, in fact the stack is not even necessary at all, but we’ll keep it in since we’re just aligning one view out of four.

view with alignment on frame

This does exactly what we want.

Now we can bring back the other three Image views, using the ZStack to overlay them together, but specifying different alignment for each:

struct ContentView: View {
    var body: some View {
        ZStack {
            Image("brawler")
                .frame(minWidth: 0, maxWidth: .infinity,
                       minHeight: 0, maxHeight: .infinity,
                       alignment: .topLeading)
            Image("rogue")
                .frame(minWidth: 0, maxWidth: .infinity,
                       minHeight: 0, maxHeight: .infinity,
                       alignment: .topTrailing)
            Image("barbarian")
                .frame(minWidth: 0, maxWidth: .infinity,
                       minHeight: 0, maxHeight: .infinity,
                       alignment: .bottomLeading)
            Image("dragoon")
                .frame(minWidth: 0, maxWidth: .infinity,
                       minHeight: 0, maxHeight: .infinity,
                       alignment: .bottomTrailing)
        }
    }
}

Each Image has its fixed size, and each is surrounded by a .frame that explodes out the surrounding ZStack to the proposed size of its own parent. Alignment is specified for each frame individually, to align the Image within it:

view with four images and alignment on frames

Imagery used in previews by Kaiseto, original images and derived here licensed under Creative Commons 3.0 BY-NC-SA.