Spatial SwiftUI: Layout Depth Alignment

We can use depth alignment on layouts to align views in a 3D space.

Overview

There is a new instance method on SwiftUI Layouts called .depthAlignment. When using a view based on Layout, we can use this method to align our subviews along the Z Axis. This is similar to how we use alignment in SwiftUI stacks, but with a little extra help for 3D content.

Let’s see this in action, then go over the details.

depthAlignment demo

This window has a VStackLayout with two rows. Each row is an HStackLayout. We’re using the layout variants of these stacks to get access to depthAlignment.

VStackLayout().depthAlignment(alignment) {
  // Row 1
  HStackLayout().depthAlignment(alignment) {
  ...
  }
  // Row 2
  HStackLayout().depthAlignment(alignment) {
  ...
  }
}

As you can see in the video, the Earth model is our largest view. SwiftUI will use the depth of the Earth model view as the bounds. We’ll align the other views to this one.

  • The moon card is aligned to the moon model by a HStackLayout
  • The earth card is aligned to the earth model by a HStackLayout
  • Both HStackLayout views are aligned to by the outer VStackLayout
VStackLayout().depthAlignment(alignment) {

    // Using depthAlignment here will align the SwiftUI card with the Earth model
    HStackLayout().depthAlignment(alignment) {

        VStack {
            Text("Earth")
                .font(.title)
            Text("The only known planet known to serve ice cream.")
                .font(.caption)
        }

        // The largest view in this scene, all other content will align to this
        ModelView(name: "Earth")
            .frame(width: 150, height: 150)
            .debugBorder3D(showDebugLines ? .white : .clear)

    }

    // Using depthAlignment here will align the SwiftUI card with the Moon model
    // The moon is set to a smaller size than the earth. The alighment for this HStackLayout is controled by the parent
    HStackLayout().depthAlignment(alignment) {

        ModelView(name: "Moon")
            .frame(width: 60, height: 60)
            .debugBorder3D(showDebugLines ? .white : .clear)
        VStack {
            Text("Luna")
                .font(.title)
            Text("Made amost entirely of cheese.")
                .font(.caption)
        }
    }
}

Prior to visionOS 26, we could get a similar result. We could use a combination of ZStack, spacers, frame sizes, etc. This new feature makes this work simpler. It will be particularly useful when we start building more complex spatial layouts.

WWDC 2025 brought depthAlignment, along with some other powerful layout features. We’ll explore them all in the Spatial SwiftUI section. In the meantime, if you haven’t done so yet, go watch Compose custom layouts with SwiftUI.

Example Code

struct Example091: View {

    @State private var alignment: DepthAlignment = .back
    @State private var showDebugLines = false

    var body: some View {
        // We'll use depthAlignment on the VStackLayout to align all child views
        VStackLayout().depthAlignment(alignment) {

            // Using depthAlignment here will align the SwiftUI card with the Earth model
            HStackLayout().depthAlignment(alignment) {

                VStack {
                    Text("Earth")
                        .font(.title)
                    Text("The only known planet known to serve ice cream.")
                        .font(.caption)
                }
                .padding()
                .background(.black)
                .cornerRadius(24)
                .shadow(radius: 20)
                .frame(width: 220, height: 160)

                // The largest view in this scene, all other content will align to this
                ModelView(name: "Earth")
                    .frame(width: 150, height: 150)
                    .debugBorder3D(showDebugLines ? .white : .clear)

            }

            // Using depthAlignment here will align the SwiftUI card with the Moon model
            // The moon is set to a smaller size than the earth. The alighment for this HStackLayout is controled by the parent
            HStackLayout().depthAlignment(alignment) {

                ModelView(name: "Moon")
                    .frame(width: 60, height: 60)
                    .debugBorder3D(showDebugLines ? .white : .clear)
                VStack {
                    Text("Luna")
                        .font(.title)
                    Text("Made amost entirely of cheese.")
                        .font(.caption)
                }
                .padding()
                .background(.black)
                .cornerRadius(24)
                .shadow(radius: 20)
                .frame(width: 180, height: 140)
            }

        }
        .debugBorder3D(showDebugLines ? .white : .clear)

        // Controls to modify the example
        .ornament(attachmentAnchor: .scene(.trailing), contentAlignment: .trailing, ornament: {
            VStack(alignment: .center, spacing: 8) {
                Button(action: {
                    withAnimation {
                        alignment = .back
                    }
                }, label: {
                    Text("Back")
                })
                Button(action: {
                    withAnimation {
                        alignment = .center
                    }
                }, label: {
                    Text("Center")
                })
                Button(action: {
                    withAnimation {
                        alignment = .front
                    }
                }, label: {
                    Text("Front")
                })

                Button(action: {
                    showDebugLines.toggle()
                }, label: {
                    Text("Debug")
                })
            }
            .padding()
            .controlSize(.small)
            .glassBackgroundEffect()

        })

    }
}

#Preview {
    Example091()
}

// Adapted from Example 051 - Spatial SwiftUI: Model3D
fileprivate struct ModelView: View {

    @State var name: String = ""

    var body: some View {
        Model3D(named: name, bundle: realityKitContentBundle)
        { phase in
            if let model = phase.model {
                model
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            } else if phase.error != nil {
                Text("Could not load model \(name).")
            } else {
                ProgressView()
            }
        }
    }
}

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?