Loading Photos with Image Presentation Component

This component allows us to display Standard and Spatial Photos. We can also generate Spatial Scenes from existing photos.

Overview

We can use this to display three types of content in our RealityView scenes.

  1. Standard Photos
  2. Spatial Photos (captured on iPhone 15 Pro and later)
  3. Spatial Scenes (which can be generated from photos)

Let’s start by loading a Standard Photo. In this case, we’ll get the URL to a file in our app bundle. Then we can create the component using the URL. Just like any other component, we need to add it to an entity. Below is a minimal example.

guard let url = Bundle.main.url(forResource: "bell-01", withExtension: "jpeg") else { return }
do {
    let component = try await ImagePresentationComponent(contentsOf: url)
    entity.components.set(component)
} catch {
    print("Failed to load image: \(error)")
}

The process is similar if we want to load and display a Spatial Photo. We get a URL to the file and create the component. If all we want to do is display this as a standard photo, then we can use the code above. But we want to see a spatial effect for this one. There are two spatial viewing modes we can use with Spatial Photos: spatialStereo and spatialStereoImmersive. We can check to see of a mode is supported, then try to use it.

guard let url = Bundle.main.url(forResource: "bell-01-s", withExtension: "HEIC") else { return }
do {
    var component = try await ImagePresentationComponent(contentsOf: url)
    if(component.availableViewingModes .contains(.spatialStereo)) {
        component.desiredViewingMode = .spatialStereo
    }
    entity.components.set(component)
} catch {
    print("Failed to load image: \(error)")
}

Now let’s try converting the Standard Photo to a Spatial Scene. Again, we start with a URL to the file. Instead of passing it directly to the file, we need to create a Spatial3DImage. Make sure to call .generate() on the result or we won’t be able to view correctly. If the generation succeeds, we’ll have access to two new modes: spatial3D and spatial3DImmersive.

As of November 2025, the generation process does not work in the visionOS Simulator. Make sure to try this on a real device.

guard let url = Bundle.main.url(forResource: "bell-01", withExtension: "jpeg") else { return }
do {
    // Load the image as a Spatial3DImage
    let converted = try await ImagePresentationComponent.Spatial3DImage(contentsOf: url)
    // The call generate. Note that this always fails in the Simulator as of November 2025. Make sure you test this on a device.
    try await converted.generate()

    // Create the component
    var component = ImagePresentationComponent(spatial3DImage: converted)

    if(component.availableViewingModes .contains(.spatial3D)) {
        component.desiredViewingMode = .spatial3D
    }
    entity.components.set(component)

} catch {
    print("Failed to load image: \(error)")
}

We’ll explore viewing modes in more detail soon. We’ll also look into some interesting ways to display and manipulate entities with this component.

See also

Video Demo

Example Code

struct Example127: View {
    @State var photoEntity = Entity()

    var body: some View {
        RealityView { content in
            photoEntity.setPosition([0, 1.6, -2.0], relativeTo: nil)
            photoEntity.scale = .init(repeating: 0.6)
            content.add(photoEntity)

            // Add some buttons to switch photos
            let controlMenu = Entity()
            let controlAttachment = ViewAttachmentComponent(rootView: controls)
            controlMenu.components.set(controlAttachment)
            controlMenu.setPosition([0, 1.2, -1.8], relativeTo: nil)
            content.add(controlMenu)
        }
    }
    var controls: some View {
        HStack(spacing: 12) {
            Button(action: {
                Task {
                    await loadPhoto(entity: photoEntity)
                }
            }, label: {
                Text("Photo")
            })
            Button(action: {
                Task {
                    await loadSpatialPhoto(entity: photoEntity)
                }
            }, label: {
                Text("Spatial Photo")
            })
            Button(action: {
                Task {
                    await loadPhotoToConvert(entity: photoEntity)
                }
            }, label: {
                Text("Spatial Scene")
            })
        }
        .padding()
        .background(.black)
        .clipShape(.capsule)
        .controlSize(.extraLarge)
    }

    /// Load a regular (non-spatial) photo
    func loadPhoto(entity: Entity) async {
        guard let url = Bundle.main.url(forResource: "bell-01", withExtension: "jpeg") else { return }
        do {
            let component = try await ImagePresentationComponent(contentsOf: url)
            entity.components.set(component)
        } catch {
            print("Failed to load image: \(error)")
        }
    }

    /// Load a spatial photo (captured on iPhone 17 Pro)
    func loadSpatialPhoto(entity: Entity) async {
        guard let url = Bundle.main.url(forResource: "bell-01-s", withExtension: "HEIC") else { return }
        do {
            var component = try await ImagePresentationComponent(contentsOf: url)
            if(component.availableViewingModes .contains(.spatialStereo)) {
                component.desiredViewingMode = .spatialStereo
            }
            entity.components.set(component)

        } catch {
            print("Failed to load image: \(error)")
        }
    }

    /// Load a regular (non-spatial) photo, then convert it to a Spatial Scene
    func loadPhotoToConvert(entity: Entity) async {
        guard let url = Bundle.main.url(forResource: "bell-01", withExtension: "jpeg") else { return }
        do {
            // Load the image as a Spatial3DImage
            let converted = try await ImagePresentationComponent.Spatial3DImage(contentsOf: url)
            // The call generate. Note that this always fails in the Simulator as of November 2025. Make sure you test this on a device.
            try await converted.generate()

            // Create the component
            var component = ImagePresentationComponent(spatial3DImage: converted)

            if(component.availableViewingModes .contains(.spatial3D)) {
                component.desiredViewingMode = .spatial3D
            }
            entity.components.set(component)

        } catch {
            print("Failed to load image: \(error)")
        }
    }
}

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?