Generally speaking, you'll first want to make sure that you've added Apple Vision as a supported destination in your project's General settings. It wasn't 100% clear as to whether you were building for "Apple Vision (Designed for iPad)" or "Apple Vision (visionOS)". The former is just going to run your now-visionOS compatible app in a 2D window, whereas the latter will allow you to add the necessary customizations for visionOS and create a more immersive 3D experience, which is what you'll want.
I think the answer to your question is dependent on how your app is displaying its window. Ideally, you'd want to use an ImmersiveSpace
that embeds your main window, then set your 360-degree background as the background of the ImmersiveSpace
. Assuming you're using the SwiftUI app lifecycle, it might help to see what your @Main
entry looks like. For the sake of brevity, lets presume that your entry point is something like this, where BuildMeditationView()
is the SwiftUI view you showed in your screenshot;
@main
struct MyMeditationApp: App {
var body: some Scene {
WindowGroup {
BuildMeditationView()
}
}
}
You could modify this so that your app launches an ImmersiveSpace
as its default window;
@main
struct MyMeditationApp: App {
var body: some Scene {
WindowGroup {
#if os(visionOS)
ImmersiveSpace(id: "BuildMeditationSpace") {
ImmersiveMeditationBuilder()
}.immersionStyle(selection: .constant(.full), in: .full)
#else
BuildMeditationView()
#endif
}
}
}
You can learn more about the ImmersiveSpace in the developer docs, which has great detail on how to modify your app's Info.plist if you do want the app to launch an ImmersiveSpace at launch, as well as what the different immersion styles are.
You could then create a new SwiftUI view, ImmersiveMeditationBuilder
, that serves as your ImmersiveSpace
, and embed your original view into it. There are a few different ways to do this, one of my preferred ways is to add your original SwiftUI view as an attachment to a RealityView
;
struct ImmersiveMeditationBuilder: View {
var body: some View {
ZStack {
RealityView { content, attachments in
// Set up your video background file and player
let asset = AVURLAsset(url: Bundle.main.url(forResource: "myVideoBackgroundFile", withExtension: "mp4")!)
let playerItem = AVPlayerItem(asset: asset)
let player = AVPlayer()
// Attach the material to a large sphere that will serve as the skybox.
let entity = Entity()
entity.components.set(ModelComponent(
mesh: .generateSphere(radius: 1000),
materials: [VideoMaterial(avPlayer: player)]
))
// Ensure the texture image points inward at the viewer.
entity.scale *= .init(x: -1, y: 1, z: 1)
// Add the background to the main content.
content.add(entity)
// Add the BuildMeditationView attachment
if let attachment = attachments.entity(for: "myMeditationBuilderView") {
content.add(attachment)
}
// Play the video background
player.replaceCurrentItem(with: playerItem)
player.play()
} update: content, attachments in
// Update if necessary when the view is reloaded
} attachments: {
Attachment(id: "myMeditationBuilderView") {
BuildMeditationView()
}
}
}
}
}
This assumes you want the view you showed in your screenshot to be the main view at your app's launch. If not, you could also launch your ImmersiveSpace
based on some user action, which is detailed in Presenting Windows and Spaces developer article.