SwiftUI: Layout & Interfaces

Nov 18 2021 Swift 5.5, iOS 15, Xcode 13

Part 2: Aligning Views

15. ZStacks

Episode complete

Play next episode

Next
Save for later
About this episode
See versions

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 14. Challenge: Custom Alignment Guides Next episode: 16. GeometryReader

This course was originally recorded in 2019. It has been reviewed and all content and materials updated as of October 2021.

Now that you’ve mastered the first two dimensions, with HStacks and VStacks, it’s time to tackle the third!

And even though HStack isn’t called “XStack”, and VStack isn’t called “YStack”, the Stack you use to put things in this dimensions is called “ZStack.” Despite the new axis, it’s got a lot in common with the Stacks you already know and love.

With an HStack, the code that comes first is at the leading edge of the view. And from there, anything you add below, in code, is closer to the trailing edge.

VStacks are even more literal. The views that are on top, in code, are on top, onscreen. And for Views coded below, they show up lower onscreen.

For ZStacks, whatever code you write first represents the “background” of the view. And as you add more code below, Views stack in front of each other, one by one.

The other main difference of ZStacks is that instead of only using horizontal or vertical alignments, they use a combination of both. The structure that you use to combine a HorizontalAlignment, and a VerticalAlignment, is just called, simply, “Alignment”.

Although you can create Alignments using that initializer, there are 9 built-in values that represent the most common 3 by 3 set of points you might use.

Notably missing is anything having to do with text baselines. But if that’s something you find yourself needing often, well, you’ve learned how to make your own Alignments. So you’re all set!

But in this episode, you’ll be exploring what you can do —and that’s a lot!— with a couple of the ones Apple has provided.

Here we have an Image of Ray Wenderlich himself holding a real-world VStack of our books. Below him, is a SwiftUI VStack, containing an Image and Text View. And those are nested inside another VStack. But not for long! Change that to a ZStack and let’s see what happens.

    ZStack {
      Image("RWStack")

The nested VStack is now centered in the ZStack, matching up with the center of the background Image.

That’s because the default Alignment for a ZStack uses center for both the horizontal alignment guides of its children, and the vertical ones. But we can override that default behavior.

ZStack(alignment: .) {

Let’s align based on the “bottom-leading” guides, so the VStack can move to the lower-left.

ZStack(alignment: .bottomLeading) {

And if we pad that VStack, it’ll move away from the corner a little bit, in two dimensions, within the ZStack.

          .foregroundColor(.brightSeafoam)
      }
        .padding()
    }

Now, let’s create a Linear Gradient. It’ll help visualize some of the options you have with Z-Stacking.

struct ContentView: View {
  let gradient = LinearGradient(
    gradient: <#T##Gradient#>, startPoint: <#T##UnitPoint#>, endPoint: <#T##UnitPoint#>)

  var body: some View {

For the first parameter, create a Gradient using the initializer that incorporates stops.

    gradient: Gradient(
      stops: [

      ]
    ), startPoint: <#T##UnitPoint#>, endPoint: <#T##UnitPoint#>)

One component of a Gradient Stop is a Color. I’ve written an extension in this file, to get colors from the Asset catalog. Initialize the first one with “bright seafoam”…

.init(color: .brightSeafoam, location: <#T##CGFloat#>)

…at a location of three eighths.

.init(color: .brightSeafoam, location: 3 / 8)

Sometimes the compiler doesn’t know what to do with integer literals there. Adding a “dot zero” should help it out, if you need to.

.init(color: .brightSeafoam, location: 3.0 / 8),

In a second, you’ll be able to see what “location” means. Stick with me. Just make one more stop, of “dark seafoam”…

.init(color: .darkSeafoam, location: <#T##CGFloat#>)

…at 1, which is the highest value you can use without a runtime error.

.init(color: .darkSeafoam, location: 1)

“UnitPoint” is a structure that can represent the same types of values as ZStack Alignments. We’ll start the gradient in the upper right, using “top trailing”.

      ]
    ),
    startPoint: .topTrailing, endPoint: <#T##UnitPoint#>)

…and we’ll end it at the bottom center.

    ),
    startPoint: .topTrailing, endPoint: .bottom
  )

Now that you’ve got a gradient, apply it to the ZStack, using the overlay modifier.

      }
        .padding()
    }
      .overlay(gradient)
  }

Before we get into what “overlay” is doing, let’s dissect this gradient a bit. At the top trailing edge, which is considered the “zero” location, you’ve got the “bright seafoam color”

Three 8ths of the way down to the bottom, an actual gradient begins.

It gradates to the “dark seafoam” color, at the bottom center. That’s considered the “1” location. The locations are what are called “normalized values”: they range from zero to 1.

Now, on to overlay. It’s a modifier that layers one view in front of another. And you can think of it as the complement to the background modifier, which puts a view behind another.

If you’ve got views that need to be layered in the Z-axis, and they need to be the same size, overlays and backgrounds might be a simpler way to tackle your layout, than ZStacks.

That’s almost going to be the case, with this layout. We do want the gradient to be the same size as the background image, but we don’t want it to opaquely cover it up, like it’s doing now. Instead, use the multiply blend mode.

      .overlay(gradient)
      .blendMode(.multiply)
  }

Now, you’re colorizing the entire ZStack. But if the goal is to colorize only the background image, you’ll need to move the overlay and blendMode modifiers accordingly. (Like with option-command-left bracket.)

      Image("RWStack")
        .resizable()
        .scaledToFit()
        .overlay(gradient)
        .blendMode(.multiply)

      VStack(alignment: .leading) {

And that’s visually the end result I wanted to show you. But if you’ve got a ZStack, you’re already working with the concept of layering. You don’t need to bother with the overlay modifier.

        .scaledToFit()

      gradient
        .blendMode(.multiply)

      VStack(alignment: .leading) {

Of course, that’s not right. It looks like that now, because LinearGradient is actually a type of view. And it will expand to fill its parent if left unchecked.

To let the other views in the ZStack define the gradient’s frame, you can decrease the layout priority of the gradient.

        .blendMode(.multiply)
        .layoutPriority(-1)

      VStack(alignment: .leading) {

The default is 0, so anything negative will do that.

On the subject of layout guides: although there is an Alignment type for ZStacks, that combines Horizontal and Vertical Alignments, there is no alignmentGuide modifier overload that accepts a 2-Dimensional “Alignment”.

If you want to customize your alignment within ZStacks, you’ll need to do it for each axis individually.

To illustrate that, change the ZStack’s alignment back to center.

ZStack(alignment: .center) {

Then, let’s remove the padding from the VStack…

          .foregroundColor(.brightSeafoam)
      }
    }

…and override its HorizontalAlignment guide. You’ll need to use center, to match one of the two axes of the parent ZStack Alignment.

          .foregroundColor(.brightSeafoam)
      }
        .alignmentGuide(HorizontalAlignment.center)
    }

In the trailing closure, set the guide at the trailing edge of the VSTack.

.alignmentGuide(HorizontalAlignment.center) { $0[.trailing] }

And now, that edge is lined up with the horizontal center of the background image, and the gradient. You can see that even more clearly if you align the VStack differently.

VStack(alignment: .trailing) {

If you want to move the VStack down, so that its top trailing corner is at the center of the ZStack, you’re halfway there. You just need to use a VerticalAlignment guide.

        .alignmentGuide(HorizontalAlignment.center) { $0[.trailing] }
        .alignmentGuide(VerticalAlignment.center) { $0[.top] }
    }

And that’s all we’re going to cover in this course, when it comes to SwiftUI Stacks. Great job making it this far!