Lab 028 – Custom Ornament Anchor Positions

Creating a dynamic anchor that can be moved around a window.

In the example code for Spatial SwiftUI: Window Ornaments we saw that ornament anchors are UnitPoint values. We can use the properties that SwiftUI provides (.top, .center .leading, etc.). We can also set custom values.

This will create an anchor at the top leading (left) position.

@State var ornamentAnchor: UnitPoint = .init(x: 0.0, y: 0.0)

We can scale the x and y values between 0.0 and 1.0.

This lab demos two examples

  1. Sliders to modify the x and y values
  2. An animation that will move the ornament around the window on a timer

Video Demo

visionOS simulator video using sliders to position ornament anywhere around the window

Full Lab Code

Updated October 19, 2026 to resolve isolation errors in the animation function

struct Lab028: View {

    static let info = ItemInfo(
        title: "Custom Ornament Anchor Positions",
        description: "Creating a dynamic anchor that can be moved around a window.",
        scene: .window()
    )

    @State var ornamentAnchor: UnitPoint = .init(x: 0.0, y: 0.0)
    @State private var isAutoMode = false
    @State private var autoModeTimer: Timer?
    @State private var animationPhase = 0
    
    func startAutoMode() {

        ornamentAnchor = UnitPoint(x: 0, y: 0)
        animationPhase = 0
        
        autoModeTimer = Timer.scheduledTimer(withTimeInterval: 2.5, repeats: true) { _ in
            Task { @MainActor in
                guard isAutoMode else { return }

                withAnimation(.easeInOut(duration: 2.0)) {
                    switch animationPhase {
                    case 0: // Start from Top left and move to top right
                        ornamentAnchor = UnitPoint(x: 1, y: 0)
                    case 1: // Move from top right to bottom right
                        ornamentAnchor = UnitPoint(x: 1, y: 1)
                    case 2: // Move from bottom right to bottom left
                        ornamentAnchor = UnitPoint(x: 0, y: 1)
                    case 3: // Move from bottom left to top left
                        ornamentAnchor = UnitPoint(x: 0, y: 0)
                    default:
                        break
                    }
                    animationPhase = (animationPhase + 1) % 4
                }
            }
        }
    }
    
    func stopAutoMode() {
        autoModeTimer?.invalidate()
        autoModeTimer = nil
    }

    var body: some View {
        VStack {

            VStack(alignment: .leading, spacing: 20) {
                Text("Ornament Position")
                    .font(.headline)
                
                Toggle("Auto Mode", isOn: Binding(
                    get: { isAutoMode },
                    set: { newValue in
                        isAutoMode = newValue
                        if newValue {
                            startAutoMode()
                        } else {
                            stopAutoMode()
                        }
                    }
                ))
                .padding(.bottom, 8)
                
                HStack {
                    Text("X:")
                    Slider(
                        value: Binding(
                            get: { ornamentAnchor.x },
                            set: { ornamentAnchor = UnitPoint(x: $0, y: ornamentAnchor.y) }
                        ),
                        in: 0...1
                    )
                    Text(String(format: "%.2f", ornamentAnchor.x))
                }
                .fontDesign(.monospaced)
                .disabled(isAutoMode)
                
                HStack {
                    Text("Y:")
                    Slider(
                        value: Binding(
                            get: { ornamentAnchor.y },
                            set: { ornamentAnchor = UnitPoint(x: ornamentAnchor.x, y: $0) }
                        ),
                        in: 0...1
                    )
                    Text(String(format: "%.2f", ornamentAnchor.y))
                }
                .fontDesign(.monospaced)
                .disabled(isAutoMode)
            }
            .frame(width: 300)
            .padding()

        }
        .ornament(attachmentAnchor: .scene(ornamentAnchor)) {
            Text("Ornament")
                .padding(20)
                .background(.stepBlue)
                .cornerRadius(20)
        }
    }
}

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

Questions or feedback?