Platform features

Camera apps and the outer display

iPhone Duo camera development: the Virtual Front Camera, inner and outer ultrawide cameras, AVCaptureDeviceDirectionCoordinator, and CameraCaptureAccessory for showing UI on the outer display.

8 min readUpdated 4 sources

iPhone Duo has two front cameras, both square-sensor ultrawides:

Camera Where Up to
Outer ultrawide Outside of the device 4K at 120 fps
Inner ultrawide Under the inner display (the first under-display camera on iPhone) 1080p at 60 fps

It’s also the first iPhone where “front” doesn’t always mean “facing the person”. Flip the phone around while it’s closed and the rear cameras face whoever is looking at the screen.1

The easy path: the Virtual Front Camera

Ask AVCaptureDeviceDiscoverySession for a .front camera of type .builtInWideAngleCamera or .builtInUltraWideCamera, as you do today. On iPhone Duo you get the Virtual Front Camera, which switches between the inner and outer cameras by itself as the device opens and closes.1

It exposes only what both cameras share: up to 1080p at 60 fps, and no depth. For many apps (video calls, selfies, scanning) that’s all you need, and you don’t have to change anything.1

Full control: individual cameras

For 4K, 120 fps or depth, use the physical cameras directly with .builtInOuterUltraWideCamera and .builtInInnerUltraWideCamera. Your app then has to switch cameras itself when the device opens or closes.1

Know which way each camera faces

AVCaptureDevice.position reports both front cameras as .front, which no longer tells you whether a camera faces the user. AVCaptureDeviceDirectionCoordinator does, relative to a view, and tells you when that changes:

directionCoordinator = AVCaptureDeviceDirectionCoordinator(
    view: view,
    deviceTypes: [
        .builtInOuterUltraWideCamera,
        .builtInInnerUltraWideCamera,
        .builtInDualWideCamera,
    ],
    changeHandler: { [weak self] map in
        self?.updateCameraSession(map)
    }
)

In the change handler:

  1. Reconfigure your AVCaptureSession to keep streaming from the camera that now faces the user.
  2. Update mirroring. Mirror the preview when a rear camera faces the user, so it feels like a selfie.
  3. Update your UI.

The coordinator is tied to a view, so it runs on the main actor. Don’t call AVFoundation from the handler. Pass the AVCaptureDeviceDescriptor it gives you (a Sendable description of the device) to your camera actor and create the AVCaptureDevice there.1

Polish the preview

  • Spare space. Streaming the full field of view leaves room around the preview. Use videoGravity on AVCaptureVideoPreviewLayer to offset it for your controls, or to fill the display.
  • Fill the inner display. The square sensors let you pick a landscape aspect ratio with dynamicAspectRatio on AVCaptureDevice.4
  • Rotation. Adopt AVCaptureDeviceRotationCoordinator to keep previews and photos upright. On iPhone Duo it also updates when your app moves between displays.1
  • Performance. Sensor orientation compensation is on for every iPhone Duo front camera. Turn off isCameraSensorOrientationCompensationEnabled on AVCapturePhotoOutput if you handle orientation yourself.
  • The inner camera is a reserved region. While it’s active, keep custom controls out of its occlusion region.

Showing UI on the outer display

Camera apps are the only third-party apps that can show their own UI on the outer display while the inner one is in use: a teleprompter facing the speaker, or something to make a child look at the lens. You do it with a camera capture accessory, registered on the same view as your camera UI.12

It shows only when your app is full screen on the inner display, in the foreground, with an active capture session. The system can withdraw it at any time, such as when the phone closes.2

struct CameraRootView: View {
    @State private var model = TeleprompterModel()

    var body: some View {
        CameraView(model: model)
            .sceneAccessory {
                CameraCaptureAccessory(isEnabled: $model.isEnabled) {
                    TeleprompterView(model: model)
                }
                .onAvailabilityChange { model.isAvailable = $0 }
            }
            .toolbar {
                TeleprompterToggle(isEnabled: $model.isEnabled)
                    .disabled(!model.isAvailable)
            }
    }
}

In UIKit, register a UISceneAccessory with registerSceneAccessory(_:) and toggle it with UISceneAccessoryRegistration.isEnabled. Accessories are enabled by default.2

If you show live content on both displays, use one UIView per display and one direction coordinator per view. Each coordinator reports directions relative to its own view, so the outer display’s coordinator sees the rear cameras as facing forward.1

Not a camera app?

Non-camera apps can’t draw on the outer display. The other ways to appear there are system surfaces: Live Activities (an AlarmKit countdown, for example) and the Dynamic Island.3

Sources

  1. Tech Talk: Build a great camera experience for iPhone Duo
  2. Tech Talk: Leverage multiple displays and scenes on iPhone Duo
  3. Apple Developer Forums: A summary of the iPhone Duo Group Lab
  4. WWDC26: Support the Center Stage front camera in your iOS app