2025 DesignSpatial ComputingSwiftUI & UI Frameworks
WWDC25 · 20 min · Design / Spatial Computing / SwiftUI & UI Frameworks
Meet SwiftUI spatial layout
Explore new tools for building spatial experiences using SwiftUI. Learn the basics of 3D SwiftUI views on visionOS, customize existing layouts with depth alignments, and use modifiers to rotate and position views in space. Discover how to use spatial containers to align views in the same 3D space, helping you create immersive and engaging apps.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 26 snippets
Robot Image Frame
// Some views have fixed frames
Image("RobotHead")
.border(.red) Color Frame
// Some views have flexible frames
Color.blue
.border(.red) Layout Composed Frame
// Layouts compose the frames of their children
VStack {
Image("RobotHead")
.border(.red)
Image("RobotHead")
.border(.red)
}
.border(.yellow) Model3D Frame
// Some views have fixed depth
Model3D(named: "Robot")
.debugBorder3D(.red) Zero Depth Views
// Many views have 0 depth
HStack {
Image("RobotHead")
.debugBorder3D(.red)
Text("Hello! I'm a piece of text. I have 0 depth.")
.debugBorder3D(.red)
Color.blue
.debugBorder3D(.red)
.frame(width: 200, height: 200)
} RealityView Depth
// RealityView takes up all available space including depth
RealityView { content in
// Setup RealityView content
}
.debugBorder3D(.red) GeometryReader3D Depth
// GeometryReader3D uses all available depth
GeometryReader3D { proxy in
// GeometryReader3D content
}
.debugBorder3D(.red) Model3D scaledToFit3D
// Scaling a Model3D to fit available space
Model3D(url: robotURL) {aresolved in
resolved.resizable()
}aplaceholder: {
ProgressView()
}
.scaledToFit3D()
.debugBorder3D(.red) ZStack depth
// ZStack composes subview depths
ZStack {
Model3D(named: "LargeRobot")
.debugBorder3D(.red)
Model3D(named: "BabyBot")
.debugBorder3D(.red)
}
.debugBorder3D(.yellow) ZStack with RealityView
// ZStack composes subview depths
ZStack {
RealityView { ... }
.debugBorder3D(.red)
Model3D(named: "BabyBot")
.debugBorder3D(.red)
}
.debugBorder3D(.yellow) Layouts are 3D
// HStack also composes subview depths
HStack {
Model3D(named: "LargeRobot")
.debugBorder3D(.red)
Model3D(named: "BabyBot")
.debugBorder3D(.red)
}
.debugBorder3D(.yellow) ResizableRobotView
struct ResizableRobotView: View {
let asset: Model3DAsset
var body: some View {
Model3D(asset: asset) { resolved in
resolved
.resizable()
}
.scaledToFit3D()
}
} Robot Profile 1
//`Layout` types back align views by default
struct RobotProfile: View {
let robot: Robot
var body: some View {
VStack {
ResizableRobotView(asset: robot.model3DAsset)
RobotNameCard(robot: robot)
}
.frame(width: 300)
}
} Customizing Vertical Alignment
// Customizing vertical alignment
HStack(alignment: .bottom) {
Image("RobotHead")
.border(.red)
Color.blue
.frame(width: 100, height: 100)
.border(.red)
}
.border(.yellow) Customizing Depth Alignment
// Customizing depth alignments
struct RobotProfile: View {
let robot: Robot
var body: some View {
VStackLayout().depthAlignment(.front) {
ResizableRobotView(asset: robot.model3DAsset)
RobotNameCard(robot: robot)
}
.frame(width: 300)
}
} Robot Favorite Row
struct FavoriteRobotsRow: View {
let robots: [Robot]
var body: some View {
HStack {
RobotProfile(robot: robots[2])
RobotProfile(robot: robots[0])
RobotProfile(robot: robots[1])
}
}
} Custom Depth Alignment ID
// Defining a custom depth alignment guide
struct DepthPodiumAlignment: DepthAlignmentID {
static func defaultValue(in context: ViewDimensions3D) -> CGFloat {
context[.front]
}
}
extension DepthAlignment {
static let depthPodium = DepthAlignment(DepthPodiumAlignment.self)
} Customizing Depth Alignment Guides
// Views can customize their alignment guides
struct FavoritesRow: View {
let robots: [Robot]
var body: some View {
HStackLayout().depthAlignment(.depthPodium) {
RobotProfile(robot: robots[2])
RobotProfile(robot: robots[0])
.alignmentGuide(.depthPodium) {
$0[DepthAlignment.back]
}
RobotProfile(robot: robots[1])
.alignmentGuide(.depthPodium) {
$0[DepthAlignment.center]
}
}
}
} Rotation3DEffect
// Rotate views using visual effects
Model3D(named: "ToyRocket")
.rotation3DEffect(.degrees(45), axis: .z) Rotation3DLayout
// Rotate using any axis or angle
HStackLayout().depthAlignment(.front) {
RocketDetailsCard()
Model3D(named: "ToyRocket")
.rotation3DLayout(.degrees(isRotated ? 45 : 0), axis: .z)
} Pet Radial Layout
// Custom radial Layout
struct PetRadialLayout: View {
let pets: [Pet]
var body: some View {
MyRadialLayout {
ForEach(pets) { pet in
PetImage(pet: pet)
}
}
}
} Rotated Robot Carousel
struct RobotCarousel: View {
let robots: [Robot]
var body: some View {
VStack {
Spacer()
MyRadialLayout {
ForEach(robots) { robot in
ResizableRobotView(asset: robot.model3DAsset)
.rotation3DLayout(.degrees(-90), axis: .x)
}
}
.rotation3DLayout(.degrees(90), axis: .x)
}
} Spatial Container
// Aligning views in 3D space
SpatialContainer(alignment: .topTrailingBack) {
LargeBox()
MediumBox()
SmallBox()
} Spatial Overlay
// Aligning overlayed content
LargeBox()
.spatialOverlay(alignment: .bottomLeadingFront) {
SmallBox()
} Selection Ring Spatial Overlay
struct RobotCarouselItem: View {
let robot: Robot
let isSelected: Bool
var body: some View {
ResizableRobotView(asset: robot.model3DAsset)
.spatialOverlay(alignment; .bottom) {
if isSelected {
ResizableSelectionRingModel()
}
}
} DebugBorder3D
extension View {
func debugBorder3D(_ color: Color) -> some View {
spatialOverlay {
ZStack {
Color.clear.border(color, width: 4)
ZStack {
Color.clear.border(color, width: 4)
Spacer()
Color.clear.border(color, width: 4)
}
.rotation3DLayout(.degrees(90), axis: .y)
Color.clear.border(color, width: 4)
}
}
} Resources
Related sessions
-
30 min -
40 min -
27 min