Add Center Stage Framing To An iOS Camera App
Center Stage on iPhone is not just a video-call effect anymore. On iPhone 17, iPhone Air, and iPhone 17 Pro, the front camera uses a square sensor with a 95-degree field of view. That changes what a camera app can offer: portrait and landscape selfies without rotating the phone, wider group shots, automatic face-aware framing, and better stabilization for calls.
The practical question is not “how do I turn on Center Stage everywhere?” It is “which framing behavior makes sense for this moment in my app?”
A creator camera may want a visible Tap to Rotate button because people compose deliberately. A family selfie flow may want automatic framing that widens when more people enter. A scanner probably should not auto-rotate or zoom at all, because the document edges are the subject. A telehealth, tutoring, or video-call app should lean on Center Stage so the person stays centered while moving, then add a clear fallback when the device cannot support it.
The iOS 26 and WWDC26 API names below are beta-shaped. The concepts and flow are stable enough to design around, but check the exact labels, availability annotations, enum cases, and throwing behavior in the current Xcode documentation before shipping.
The framing model
Treat framing as a small product model instead of a camera toggle.
For a photo-oriented front camera, the useful modes are:
- Unsupported fallback: use your existing front camera path, hide dynamic framing controls, and keep manual zoom or crop tools if you already have them.
- Manual ratio: let the user switch between supported ratios like 3x4, 4x3, 9x16, 16x9, or 1x1.
- Smart framing: let the system recommend a ratio and zoom factor based on face and gaze detection.
- Video-call Center Stage: use supported Center Stage formats and the process-wide Center Stage controls.
Those modes should not all appear in every app.
In a selfie or creator camera, manual controls are valuable. A user may want 9x16 for Reels, 1x1 for a profile image, or 4x3 for a higher-resolution keepsake. Automatic framing can be a separate “Auto” toggle.
In a scanner, the safer decision is usually to disable automatic framing. The app should keep the preview stable, avoid unexpected aspect-ratio changes, and let document detection or a crop editor do the work.
In telehealth, tutoring, education, fitness, and video-call apps, Center Stage is about continuity. If someone stands up, steps to a whiteboard, or another person joins, the call should stay comfortable without asking the user to recompose.
Pick the camera and format
For photos, start by finding the front .builtInUltraWideCamera. On the Center Stage front camera, dynamic aspect ratio is exposed through formats that report supportedDynamicAspectRatios.
Not every format supports every ratio. Apple showed examples for 3x4, 4x3, 9x16, 16x9, and 1x1, but the 4032 photo format supports 3x4 and 4x3. Query formats instead of hard-coding assumptions.
import AVFoundation
@available(iOS 26.0, *)
enum FramingRatio: CaseIterable {
case portrait4x3
case landscape4x3
case portrait16x9
case landscape16x9
case square
// The Objective-C headers expose AVCaptureAspectRatio; Swift imports it
// as AVCaptureDevice.AspectRatio in the iOS 26 SDK.
var dynamicAspectRatio: AVCaptureDevice.AspectRatio {
switch self {
case .portrait4x3:
return .ratio3x4
case .landscape4x3:
return .ratio4x3
case .portrait16x9:
return .ratio9x16
case .landscape16x9:
return .ratio16x9
case .square:
return .ratio1x1
}
}
}
@available(iOS 26.0, *)
struct CenterStageCameraPicker {
static func frontUltraWideCamera() -> AVCaptureDevice? {
let session = AVCaptureDevice.DiscoverySession(
deviceTypes: [.builtInUltraWideCamera],
mediaType: .video,
position: .front
)
return session.devices.first
}
static func format(
on camera: AVCaptureDevice,
supporting ratio: AVCaptureDevice.AspectRatio
) -> AVCaptureDevice.Format? {
camera.formats.first { format in
format.supportedDynamicAspectRatios.contains(ratio)
}
}
static func supportedRatios(on camera: AVCaptureDevice) -> [FramingRatio] {
FramingRatio.allCases.filter { ratio in
format(on: camera, supporting: ratio.dynamicAspectRatio) != nil
}
}
}
That helper gives your UI a clean capability boundary. If frontUltraWideCamera() returns nil, or supportedRatios is empty, show the regular front-camera experience. Do not show a disabled Center Stage button that cannot explain itself. A small “Framing controls are available on supported front cameras” message in settings is enough.
When you build the session, keep the old AVFoundation discipline: configure inputs and outputs on your camera queue, lock the device only around configuration changes, and rebuild only when you actually change the session topology.
import AVFoundation
@available(iOS 26.0, *)
final class FrontCameraSession {
let session = AVCaptureSession()
let camera: AVCaptureDevice
let photoOutput = AVCapturePhotoOutput()
init?() throws {
guard let camera = CenterStageCameraPicker.frontUltraWideCamera() else {
return nil
}
self.camera = camera
session.beginConfiguration()
session.sessionPreset = .photo
let input = try AVCaptureDeviceInput(device: camera)
guard session.canAddInput(input), session.canAddOutput(photoOutput) else {
session.commitConfiguration()
return nil
}
session.addInput(input)
session.addOutput(photoOutput)
session.commitConfiguration()
}
}
You can still use an AVCaptureVideoPreviewLayer as usual. Dynamic aspect-ratio changes crop from the square sensor without requiring you to tear down and rebuild the preview.
Add manual Tap to Rotate
Manual framing is the first feature I would ship in a creator or selfie camera. It is predictable, it gives users control, and it makes the hardware benefit visible immediately.
The important implementation detail is to choose an active format that supports the requested ratio before calling setDynamicAspectRatio.
import AVFoundation
@available(iOS 26.0, *)
actor FramingController {
private let camera: AVCaptureDevice
init(camera: AVCaptureDevice) {
self.camera = camera
}
func apply(_ ratio: FramingRatio) async throws -> CMTime {
let dynamicRatio = ratio.dynamicAspectRatio
guard let format = CenterStageCameraPicker.format(
on: camera,
supporting: dynamicRatio
) else {
throw FramingError.unsupportedRatio
}
try camera.lockForConfiguration()
defer { camera.unlockForConfiguration() }
if camera.activeFormat != format {
camera.activeFormat = format
}
return try await camera.setDynamicAspectRatio(dynamicRatio)
}
}
enum FramingError: Error {
case unsupportedRatio
case missingSynchronizationClock
}
Your Tap to Rotate button can then cycle through the supported ratios for the current workflow.
@available(iOS 26.0, *)
func rotateSelfieFrame(
from current: FramingRatio,
supported: [FramingRatio],
controller: FramingController
) async {
guard let currentIndex = supported.firstIndex(of: current) else { return }
let nextIndex = supported.index(after: currentIndex)
let wrappedIndex = nextIndex == supported.endIndex ? supported.startIndex : nextIndex
let nextRatio = supported[wrappedIndex]
do {
_ = try await controller.apply(nextRatio)
} catch {
// Keep the current preview and surface a lightweight UI state if needed.
}
}
For a general camera app, I would not expose every ratio at once. A segmented control with Portrait, Landscape, and Square is easier to understand than a wall of numbers. For a creator app, use labels that match the destination: Story, Post, Thumbnail, Profile. Internally, still store the actual aspect ratio you applied.
Add smart framing for photos
Smart framing is different from manual framing. It is not a command from the user. It is a recommendation stream from smartFramingMonitor, based on automatic face and gaze detection.
That makes it a good fit for group selfies and hands-free creator flows. It is a bad fit for any capture where the subject is not a person, or where changing the crop would break trust.
The monitor is designed for photo capture. In Apple’s WWDC26 example, smart framing uses the 4032 photo format and the monitor is available from the camera as smartFramingMonitor. You configure enabledFramings, observe recommendedFraming, then apply the recommendation. Set the aspect ratio before the zoom factor for a smoother preview transition.
import AVFoundation
@available(iOS 26.0, *)
final class SmartPhotoFramingController {
private let camera: AVCaptureDevice
private var observation: NSKeyValueObservation?
private var monitor: AVCaptureSmartFramingMonitor?
private var recommendationTask: Task<Void, Never>?
private var isMonitoring = false
init(camera: AVCaptureDevice) {
self.camera = camera
}
func start() throws {
guard let format = camera.formats.first(where: { $0.isSmartFramingSupported }) else {
throw FramingError.unsupportedRatio
}
try camera.lockForConfiguration()
camera.activeFormat = format
guard let monitor = camera.smartFramingMonitor else {
camera.unlockForConfiguration()
throw SmartFramingError.monitorUnavailable
}
monitor.enabledFramings = monitor.supportedFramings
camera.unlockForConfiguration()
self.monitor = monitor
isMonitoring = true
observation = monitor.observe(\.recommendedFraming, options: [.new]) { [weak self] monitor, _ in
guard let self, let framing = monitor.recommendedFraming else { return }
recommendationTask?.cancel()
recommendationTask = Task { [weak self] in
guard let self, self.isMonitoring, !Task.isCancelled else { return }
do {
try self.camera.lockForConfiguration()
defer { self.camera.unlockForConfiguration() }
guard self.isMonitoring, !Task.isCancelled else { return }
_ = try await self.camera.setDynamicAspectRatio(framing.aspectRatio)
guard self.isMonitoring, !Task.isCancelled else { return }
self.camera.videoZoomFactor = CGFloat(framing.zoomFactor)
} catch {
// Drop this recommendation. The monitor will publish another one.
}
}
}
try monitor.startMonitoring()
}
func stop() {
isMonitoring = false
recommendationTask?.cancel()
recommendationTask = nil
observation?.invalidate()
observation = nil
monitor?.stopMonitoring()
monitor = nil
}
}
enum SmartFramingError: Error {
case monitorUnavailable
}
Two shipping details matter here.
First, make Auto framing a user-controlled mode. Start monitoring when the user enables Auto, and stop monitoring when they return to manual framing, leave the camera, or start a capture flow where automatic changes would be surprising.
Second, consider limiting enabledFramings to the monitor’s actual supportedFramings. Apple’s example talks about 4x3 narrow and wide framing choices for a photo flow; do not assume square smart-framing recommendations exist just because manual 1x1 dynamic aspect ratio exists elsewhere. Inspect monitor.supportedFramings, keep only the choices that make sense for the screen, and let the system recommend only from that subset.
Handle photo orientation and recording edges
The newer front sensor is portrait mounted. That matters if your app has years of front-camera orientation assumptions baked into image processing, metadata correction, previews, uploads, or tests.
AVCapturePhotoOutput applies sensor orientation compensation by default for HEIC, JPEG, and uncompressed processed photos. It does not apply that compensation to Bayer RAW or Apple ProRAW. In iOS 26, the control point Apple called out is cameraSensorOrientationCompensationEnabled.
import AVFoundation
@available(iOS 26.0, *)
func configurePhotoOrientationBehavior(_ photoOutput: AVCapturePhotoOutput) {
guard photoOutput.isCameraSensorOrientationCompensationSupported else {
return
}
// The default is already true when supported. Set this only when you are
// deliberately testing or changing the behavior.
photoOutput.cameraSensorOrientationCompensationEnabled = true
}
For most apps, leave compensation on and test the output path that matters: save to Photos, upload to your backend, render in your editor, and share through your export sheet. If you turn compensation off for performance, add explicit tests on iPhone 17, iPhone Air, and iPhone 17 Pro so old rotation assumptions do not produce sideways photos.
Video recording needs a different product decision. QuickTime movie tracks need the same dimensions for all samples. If you change dynamic aspect ratio during capture with AVCaptureMovieFileOutput, recording may stop. If you use AVCaptureVideoDataOutput with AVAssetWriter, use the timestamp returned by setDynamicAspectRatio to end the current segment and start a new one with the updated dimensions.
One subtle detail matters: AVFoundation reports that timestamp in the device clock. Before comparing it with sample-buffer timestamps from AVCaptureVideoDataOutput, convert it to the session’s synchronizationClock using CMSyncConvertTime. The source clock depends on the capture connection/port you are using; the AVCaptureSession.synchronizationClock documentation shows the same conversion pattern for output data.
@available(iOS 26.0, *)
func changeRecordingRatio(
to ratio: FramingRatio,
session: AVCaptureSession,
deviceClock: CMClock,
controller: FramingController,
recorder: SegmentingRecorder
) async throws {
let effectiveDeviceTime = try await controller.apply(ratio)
guard let sessionClock = session.synchronizationClock else {
throw FramingError.missingSynchronizationClock
}
let effectiveSessionTime = CMSyncConvertTime(
effectiveDeviceTime,
from: deviceClock,
to: sessionClock
)
// Your recorder owns this policy. The timestamp tells you when buffers
// with the new dimensions begin, after conversion into session time.
try await recorder.finishCurrentSegment(atSessionTime: effectiveSessionTime)
try await recorder.startNextSegment(afterSessionTime: effectiveSessionTime)
}
protocol SegmentingRecorder {
func finishCurrentSegment(atSessionTime time: CMTime) async throws
func startNextSegment(afterSessionTime time: CMTime) async throws
}
For short social clips, I would usually disable ratio changes while recording and let the user pick before capture. For a pro creator app, segmented recording can be worth the complexity if you can make the edit/export model clear. For a scanner or compliance workflow, lock the framing during capture.
Enable Center Stage for video calls
Video calls are where Center Stage should feel automatic.
If your conferencing app uses the Voice over IP background mode, the system already gives people the Control Center Video Effects entry point. For custom call stacks, still configure a supported Center Stage format, then set the process-wide Center Stage controls.
import AVFoundation
@available(iOS 26.0, *)
func enableCenterStageForCall(
camera: AVCaptureDevice,
videoConnection: AVCaptureConnection
) throws {
guard let centerStageFormat = camera.formats.first(where: { $0.isCenterStageSupported }) else {
throw CenterStageCallError.unsupportedDevice
}
try camera.lockForConfiguration()
camera.activeFormat = centerStageFormat
camera.unlockForConfiguration()
// Control Center is the default user control surface.
// Cooperative mode lets your app offer a button too.
AVCaptureDevice.centerStageControlMode = .cooperative
AVCaptureDevice.isCenterStageEnabled = true
if camera.activeFormat.isVideoStabilizationModeSupported(.lowLatency) {
videoConnection.preferredVideoStabilizationMode = .lowLatency
}
let centerStageIsActuallyActive = camera.isCenterStageActive
// Reflect this in your UI, and observe it if your session can reconfigure.
}
enum CenterStageCallError: Error {
case unsupportedDevice
}
Use .cooperative when your app has its own visible Center Stage button and you want Control Center to remain a valid control. Use app-only control when the call experience requires a single source of truth. Either way, reflect the actual state in your UI and expect the user to change system video effects outside your app.
Also distinguish “enabled” from “active.” AVCaptureDevice.isCenterStageEnabled is process-wide intent. camera.isCenterStageActive tells you whether the selected device and current configuration are actually using Center Stage. Some capture features, including depth delivery and geometric distortion correction, can be mutually exclusive with Center Stage, so observe active state if your call session can change formats or outputs.
Low-latency stabilization is specifically useful for calls because it improves walking, tutoring, fitness, and handheld movement without adding the kind of delay that would feel strange in conversation.
Source trail
The source trail for this implementation is Apple’s WWDC26 session “Support the Center Stage front camera in your iOS app” and the AVFoundation documentation, especially the guide for Supporting Center Stage front camera in your iOS app, AVCaptureDevice, AVCaptureDevice.Format, AVCapturePhotoOutput, AVCaptureConnection, AVCaptureMovieFileOutput, and AVCaptureVideoDataOutput.
Because these APIs are new in the iOS 26 SDK cycle, verify the exact symbols in the documentation that ships with your Xcode seed. The names in this post follow Apple’s WWDC26 transcript and sample shape, but beta APIs are allowed to move.
Shipping checklist
Before you ship, make the behavior explicit:
- Query support from the camera and active formats. Do not branch only on device model.
- Hide manual framing controls when no dynamic aspect-ratio format is available.
- Decide which ratios belong in each product flow. A creator camera, scanner, profile-photo picker, and video call do not need the same UI.
- Apply dynamic aspect ratio before zoom factor when accepting smart-framing recommendations.
- Start
smartFramingMonitoronly while Auto framing is on, and stop it during cleanup. - Test HEIC, JPEG, processed uncompressed, RAW, and ProRAW paths separately if your app supports them.
- Keep dynamic aspect ratio fixed during simple movie recording, or segment recordings with the timestamp returned by
setDynamicAspectRatioafter converting it intoAVCaptureSession.synchronizationClocktime. - For calls, choose Control Center-only, cooperative, or app-controlled Center Stage deliberately.
- For calls, observe whether Center Stage is active, not just whether it was requested.
- Add unsupported-device copy that explains the missing capability without making the camera feel broken.
- Test on at least one supported device and one unsupported device before exposing the feature broadly.
Center Stage framing is strongest when it disappears into the capture flow. Give people manual control when composition matters, automatic help when faces are the subject, and a quiet fallback everywhere else.