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:
- Reconfigure your
AVCaptureSessionto keep streaming from the camera that now faces the user. - Update mirroring. Mirror the preview when a rear camera faces the user, so it feels like a selfie.
- 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
videoGravityonAVCaptureVideoPreviewLayerto 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
dynamicAspectRatioonAVCaptureDevice.4 - Rotation. Adopt
AVCaptureDeviceRotationCoordinatorto 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
isCameraSensorOrientationCompensationEnabledonAVCapturePhotoOutputif 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