Lab 064 – First look at Presentation Component

We can show SwiftUI views as popovers relative to the transform of a RealityKit Entity.

Overview

This component lets us present modal SwiftUI views positioned relative to a RealityKit Entity. This is similar to attachments, but with some extra magic for state and anchor position.

When we use this component on an entity, the transform for that entity will be used as the anchor point for the content. For example, we can add a card to a rocket model.

let presentation = PresentationComponent(
  isPresented: $showingPopover,
  configuration: .popover(arrowEdge: .bottom),
  content: RocketCard()
)
rocket(presentation) //❌ the card may not appear where you expect

The code works, but there is an issue. The pivot point for the rocket is on the bottom of the model. Our 2D view is right in the middle of the 3D model.

Incorrect popover placement

We can fix this just like we would when using RealityKit attachments, by using another entity for the transform. We’ll add a child entity (visualized here as a small sphere) and position it where we can the popover to appear.

Well add the component to this entity instead of the rocket.

let presentation = PresentationComponent(
  isPresented: $showingPopover,
  configuration: .popover(arrowEdge: .bottom),
  content: RocketCard()
)
presentationPoint(presentation) // ✅

We have two ways we can present and dismiss these views.

Using a SwiftUI binding.

@State private var showingPopover: Bool = false

let presentation = PresentationComponent(
  isPresented: $showingPopover,
  configuration: .popover(arrowEdge: .bottom),
  content: RocketCard()
)

Or we can call .isPresented directly on the component.

let presentation = PresentationComponent(
  configuration: .popover(arrowEdge: .bottom),
  content: RocketCard()
)

// Later, when we need to trigger the presentation
presentation.isPresented.toggle()

Full Lab Code

Overall, I think this is a welcome addition to RealityKit. We could already achieve similar results with attachments, but using PresentationComponent is a bit more convenient.

struct Lab064: View {

    @State private var subject = Entity()
    @State private var showingPopover: Bool = false

    var body: some View {
        RealityView { content in

            guard let scene = try? await Entity(named: "PresentationComponentLab", in: realityKitContentBundle) else { return }
            content.add(scene)
            scene.position.y = -0.4

            // The main entity we are looking at
            guard let subject = scene.findEntity(named: "ToyRocket") else { return }
            self.subject = subject

            // A secondary entity that we can use as the transform point for the presented popover
            guard let presentationPoint = scene.findEntity(named: "PresentationPoint") else { return }

            // We'll use a TapGesture and the new GestureComponent to toggle the popover
            let tapGesture = TapGesture()
                .onEnded({
                    Entity.animate(.bouncy, body: {
                        showingPopover.toggle()
                    })
                })
            let gestureComponent = GestureComponent(tapGesture)
            subject.components.set(gestureComponent)

            // Create the presentation component using $showingPopover to toggle presentation
            let presentation = PresentationComponent(
                isPresented: $showingPopover,
                configuration: .popover(arrowEdge: .bottom),
                content: RocketCard()
            )
            presentationPoint.components.set(presentation)

        }

    }
}

fileprivate struct RocketCard: View {
    var body: some View {
        VStack(spacing: 24) {
            Text("Rocket")
                .font(.largeTitle)

            Text("🚀🚀🚀🚀🚀")
                .font(.largeTitle)
        }
        .foregroundStyle(.black)
        .textCase(.uppercase)
        .padding()
    }
}

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?