Spatial SwiftUI: Presentation Breakthrough Effect

We can use this new modifier to override the system default breakthrough effect for presentations.

We learned how to use Breakthrough Effect to make sure our SwiftUI content stays visible. Presentations will use breakthrough effects by default in most cases. If we want to remove or customize the effects, we can use presentationBreakthroughEffect.

fileprivate struct EarthCard: View {
    var body : some View {
        VStack {
            Text("Earth")
                .font(.title)
            Text("The only known planet known to serve ice cream.")
                .font(.caption)
        }
        .padding()
        .presentationBreakthroughEffect(.prominent)
    }
}

The list of effects are the same as breakthroughEffect.

  • automatic: let visionOS decide in an effect should be applied (default even when not using this modifier)
  • none: never apply an effect
  • subtle: apply a subtle effect
  • prominent: apply a prominent effect

This effect works much the same way as breakthroughEffect. We can use that to add effects to view. We can use presentationBreakthroughEffect to remove or customize effects from presented views.

Note: make sure you try this on a real device. There has been a known issue in the visionOS Beta release notes that this feature does not work in the Simulator.

Video Demo

Example Code

fileprivate struct EarthCard: View {

    @Binding var effect: BreakthroughEffect

    var body : some View {
        VStack {
            Text("Earth")
                .font(.title)
            Text("The only known planet known to serve ice cream.")
                .font(.caption)
        }
        .padding()
        .presentationBreakthroughEffect(effect)
    }

}

struct Example099: View {

    @State private var effect: BreakthroughEffect = .none
    @State private var showingPopover: Bool = false

    var body: some View {
        VStack {
            Spacer()
            RealityView { content in

                guard let scene = try? await Entity(named: "PresentingEarth", in: realityKitContentBundle) else { return }
                content.add(scene)
                guard let subject = scene.findEntity(named: "Earth") else { return }
                guard let moon = scene.findEntity(named: "Moon") else { return }
                moon.components.set(ManipulationComponent())


                // 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: EarthCard(effect: $effect)
                )
                presentationPoint.components.set(presentation)

            }
            .realityViewLayoutBehavior(.fixedSize)
        }
        // Controls to modify the example
        .ornament(attachmentAnchor: .scene(.bottomTrailing), contentAlignment: .bottomTrailing, ornament: {
            VStack(alignment: .center, spacing: 8) {
                Button(action: {
                    withAnimation {
                        effect = .none
                    }
                }, label: {
                    Text("None")
                })
                Button(action: {
                    withAnimation {
                        effect = .subtle
                    }
                }, label: {
                    Text("Subtle")
                })
                Button(action: {
                    withAnimation {
                        effect = .prominent
                    }
                }, label: {
                    Text("Prominent")
                })

            }
            .padding()
            .controlSize(.extraLarge)
            .glassBackgroundEffect()

        })
    }
}

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?