Spatial SwiftUI: ZStack

ZStack is a core container view in SwiftUI, but it really shines in visionOS where depth is real.

The use cases for ZStack are too numerous to cover, so let’s just focus on three simple examples.

Space views evenly in the ZStack

Spacing is controlled by the parent ZStack. Each child view has a depth of 0. The stack adds space between them to create a 3D effect.

ZStack(spacing: spacing / 3) {
    RoundedRectangle(cornerRadius: 12.0)
        .foregroundStyle(.stepRed)
        .frame(width: 200, height: 200)

    RoundedRectangle(cornerRadius: 12.0)
        .foregroundStyle(.stepGreen)
        .frame(width: 150, height: 150)

    RoundedRectangle(cornerRadius: 12.0)
        .foregroundStyle(.stepBlue)
        .frame(width: 100, height: 100)
}
.frame(depth: spacing)

Use Spacers to fill all available space in the ZStack

We can push our first view to the back with a spacer. Then do the same thing with the last view. Adding two spaces between three views will result in a simple layout.

  • Back: first view is placed at a depth of 0
  • Center: second view is placed at the halfway point
  • Front: third view is paced a the max depth for the stack
ZStack() {
    RoundedRectangle(cornerRadius: 12.0)
        .foregroundStyle(.stepRed)
        .frame(width: 200, height: 200)

    Spacer() // this spaces pushes the first view all the way to the back

    RoundedRectangle(cornerRadius: 12.0)
        .foregroundStyle(.stepGreen)
        .frame(width: 150, height: 150)

    Spacer() // this spacer pushes the last view all the way to the front

    RoundedRectangle(cornerRadius: 12.0)
        .foregroundStyle(.stepBlue)
        .frame(width: 100, height: 100)
}
.frame(depth: spacing)

Child views set their own depth

In this example, each child view takes up 1/3 of the available depth.

ZStack() {
    RoundedRectangle(cornerRadius: 12.0)
        .foregroundStyle(.stepRed)
        .frame(width: 200, height: 200)
        .frame(depth: spacing / 3)

    RoundedRectangle(cornerRadius: 12.0)
        .foregroundStyle(.stepGreen)
        .frame(width: 150, height: 150)
        .frame(depth: spacing / 3)

    RoundedRectangle(cornerRadius: 12.0)
        .foregroundStyle(.stepBlue)
        .frame(width: 100, height: 100)
        .frame(depth: spacing / 3)
}
.frame(depth: spacing)

Video Demo

Full example code

struct Example036: View {

    @State var spacing: CGFloat = 0.0
    var body: some View {
        VStack(spacing: 24) {
            Slider(value: $spacing,
                   in: 0...100,
                   minimumValueLabel: Image(systemName: "square.fill"),
                   maximumValueLabel: Image(systemName: "square.stack.3d.down.forward.fill"),
                   label: {
                Text("Spacing")
            })
            .frame(width: 300)

            HStack {

                // 1. Space views evenly in the ZStack. Spacing is controlled by the parent ZStack
                ZStack(spacing: spacing / 3) {
                    RoundedRectangle(cornerRadius: 12.0)
                        .foregroundStyle(.stepRed)
                        .frame(width: 200, height: 200)

                    RoundedRectangle(cornerRadius: 12.0)
                        .foregroundStyle(.stepGreen)
                        .frame(width: 150, height: 150)

                    RoundedRectangle(cornerRadius: 12.0)
                        .foregroundStyle(.stepBlue)
                        .frame(width: 100, height: 100)
                }
                .frame(depth: spacing)


                // 2. Use Spacers to fill all available space in the ZStack
                ZStack() {
                    RoundedRectangle(cornerRadius: 12.0)
                        .foregroundStyle(.stepRed)
                        .frame(width: 200, height: 200)

                    Spacer() // this spaces pushes the first view all the way to the back

                    RoundedRectangle(cornerRadius: 12.0)
                        .foregroundStyle(.stepGreen)
                        .frame(width: 150, height: 150)

                    Spacer() // this spacer pushes the last view all the way to the front

                    RoundedRectangle(cornerRadius: 12.0)
                        .foregroundStyle(.stepBlue)
                        .frame(width: 100, height: 100)
                }
                .frame(depth: spacing)

                // 3. Each child view has a depth value that takes up 1/3 of the ZStack depth
                ZStack() {
                    RoundedRectangle(cornerRadius: 12.0)
                        .foregroundStyle(.stepRed)
                        .frame(width: 200, height: 200)
                        .frame(depth: spacing / 3)

                    RoundedRectangle(cornerRadius: 12.0)
                        .foregroundStyle(.stepGreen)
                        .frame(width: 150, height: 150)
                        .frame(depth: spacing / 3)

                    RoundedRectangle(cornerRadius: 12.0)
                        .foregroundStyle(.stepBlue)
                        .frame(width: 100, height: 100)
                        .frame(depth: spacing / 3)
                }
                .frame(depth: spacing)

            }

            .padding()
        }
    }
}

Support our work so we can continue to bring you new examples and articles.

Download the Xcode project with this and many more examples from Step Into Vision.
Some examples are provided as standalone Xcode projects. You can find those here.

Questions or feedback?