Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

Telephoto Lens Keeps Switching to Other Lenses on iPhone 16 Pro Max During PPG (Finger on Camera)

Hi,

I’m building a PPG-based heart rate feature where the user places their finger over the rear telephoto camera. On iPhone 16 Pro Max, I'm explicitly selecting the telephoto lens like this:

videoDevice = AVCaptureDevice.default(.builtInTelephotoCamera, for: .video, position: .back)

And trying to lock it:

if #available(iOS 15.0, *), 
   device.activePrimaryConstituentDeviceSwitchingBehavior != .unsupported {
    try? device.lockForConfiguration()
    device.setPrimaryConstituentDeviceSwitchingBehavior(.locked, restrictedSwitchingBehaviorConditions: [])
    device.unlockForConfiguration()
}

I also lock everything else to prevent dynamic changes:

try device.lockForConfiguration()
device.focusMode = .locked
device.exposureMode = .locked
device.whiteBalanceMode = .locked
device.videoZoomFactor = 1.0
device.automaticallyEnablesLowLightBoostWhenAvailable = false
device.automaticallyAdjustsVideoHDREnabled = false
device.unlockForConfiguration()

Despite this, the camera still switches to another lens, especially under different lighting, even though the user’s finger fully covers the lens.

Questions:

  1. How can I completely prevent lens switching in this scenario?
  2. Would using videoZoomFactor = 3.0 or 5.0 better enforce use of the telephoto lens?

Thanks! Gal

Hello @freeofself,

Based on the info you provided, I wouldn’t expect the lens to switch. Can you provide a full focused sample project that reproduces that behavior?

—Greg

Hi Greg @DTS Engineer ,

Thanks again for your response.

I understand the value of a focused sample project, but to save time, I’ve included an expanded and representative code snippet directly from my CameraManager. I hope this helps clarify what I’m doing.

In this feature, the user places a finger over the telephoto lens for PPG-based heart rate detection. I explicitly select the telephoto lens using:

AVCaptureDevice.default(.builtInTelephotoCamera, for: .video, position: .back)

Then I attempt to lock lens switching with:

device.setPrimaryConstituentDeviceSwitchingBehavior(.locked, restrictedSwitchingBehaviorConditions: [])

I also lock focus, exposure, white balance, HDR, and low light boost. I stream frame data (average RGB) to a WKWebView via evaluateJavaScript() on a repeating timer.

However, on iPhone 16 Pro Max, I still occasionally see:

Lens switching (usually in low light or when the finger fully covers the telephoto)

Or worse, the camera silently stops delivering frames (through captureOutput(_:didOutput:) )

Here’s the simplified but representative core setup:

private func setupCamera() {
    videoDevice = AVCaptureDevice.default(.builtInTelephotoCamera, for: .video, position: .back)

    if #available(iOS 15.0, *), let device = videoDevice,
       device.activePrimaryConstituentDeviceSwitchingBehavior != .unsupported {
        do {
            try device.lockForConfiguration()
            device.setPrimaryConstituentDeviceSwitchingBehavior(.locked, restrictedSwitchingBehaviorConditions: [])
            device.unlockForConfiguration()
        } catch {
            print("Lens lock failed: \(error)")
        }
    }
}

func startCamera(in view: UIView) {
    captureSession = AVCaptureSession()
    guard let captureSession, let device = videoDevice else { return }

    do {
        let input = try AVCaptureDeviceInput(device: device)
        if captureSession.canAddInput(input) {
            captureSession.addInput(input)
        }

        videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
        videoOutput.alwaysDiscardsLateVideoFrames = true
        videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "camera.queue"))

        if captureSession.canAddOutput(videoOutput) {
            captureSession.addOutput(videoOutput)
        }

        try device.lockForConfiguration()
        device.focusMode = .locked
        device.exposureMode = .locked
        device.whiteBalanceMode = .locked
        device.automaticallyAdjustsVideoHDREnabled = false
        if device.isLowLightBoostSupported {
            device.automaticallyEnablesLowLightBoostWhenAvailable = false
        }


        // Set 60 FPS 1080p format
        if let format = device.formats.first(where: {
            CMVideoFormatDescriptionGetDimensions($0.formatDescription) == CMVideoDimensions(width: 1920, height: 1080) &&
            $0.videoSupportedFrameRateRanges.first?.maxFrameRate ?? 0 >= 60
        }) {
            device.activeFormat = format
            device.activeVideoMinFrameDuration = CMTime(value: 1, timescale: 60)
            device.activeVideoMaxFrameDuration = CMTime(value: 1, timescale: 60)
        }
        device.unlockForConfiguration()

        captureSession.startRunning()

    } catch {
        print("Camera setup error: \(error)")
    }

    startSendingColorUpdatesToWebView()
}

And inside the frame callback:

func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
    guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }

    let (r, g, b) = computeAverageColor(from: pixelBuffer) ?? (0, 0, 0)
    let timestampMs = Int(Date().timeIntervalSince1970 * 1000)

    let js = """
    window.TurboNativeBridge.changeFingerAvgColorInIos(\(r), \(g), \(b), \(timestampMs))
    """
    DispatchQueue.main.async {
        self.webView?.evaluateJavaScript(js)
    }
}

My questions:

  1. Is there any way to guarantee that the system will stick to the telephoto lens under all conditions?

  2. Is there any known reason the capture session would keep running but captureOutput() would stop firing?

I’d appreciate any guidance. If necessary, I can eventually put together a full project.

Thanks again, Gal

Telephoto Lens Keeps Switching to Other Lenses on iPhone 16 Pro Max During PPG (Finger on Camera)
 
 
Q