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

Full Lab Code

struct Lab028: View {

    @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
            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.

Download the Xcode project with this and many more labs from Step Into Vision.

Questions or feedback?