Constituent active device switching very slow on iPhone 16 Pro Models on focus changes

Hi all,

we are in the business of scanning documents and barcodes with the camera system of mobile devices. Since there is a wide variety of use cases, from scanning tiniest barcodes and small business cards to scanning barcodes or large documents from far distances we preferably rely on the triple camera devices, if available, with automatic constituent device switching. This approach used to be working perfectly fine. Depending on the zoom level (we prefer to use an initial zoom value of 2.0) and the focusing distance the iPhone Pro models switched through the different camera systems at light speed: from ultra-wide to wide, tele and back. No issues at all.

Unfortunately the new iPhone 16 Pro models behave very different when it comes to constituent device switching based on focus distance. The switching is slow and sometimes it does not happen at all when the focusing distance changes. Especially when aiming for a at a distant object for a longer time and then aiming at a very close object that is maybe 2" away. The iPhone 15 Pro here always switches immediately to the ultra-wide camera, while the iPhone 16 Pro takes at least 2-3 seconds, in rare cases up to 10 seconds and sometimes forever to switch to the ultra-wide camera.

Of course we assumed that our code is responsible for these issues. So we experimented with restricting the devices and so on. Then we stripped more and more configuration code but nothing we tried improved the situation.

So we ended up writing a minimal example app that demonstrates the problem. You can find the code below. Execute it on various iPhones and aim at far distance (> 10 feet) and then quickly to very close distance (<5 inches).

Here is a list of devices and our test results:

  • iPhone 15 Pro, iOS 17.6: very fast and reliable switching
  • iPhone 15 Pro, iOS 18.1: very fast and reliable switching
  • iPhone 13 Pro Max, iOS 15.3: very fast and reliable switching
  • iPhone 16 (dual-wide camera), iOS 18.1: very fast and reliable switching
  • iPhone 16 Pro, iOS 18.1: slow switching, unreliable
  • iPhone 16 Pro Max, iOS 18.1: slow switching, unreliable

Questions:

  1. Does anyone else have seen this issue? And possibly found a workaround?

  2. Is this behaviour intended on iPhone 16 Pro models? Can we somehow improve the switching speed?

  3. Further the iPhone 16 Pro models also show a jumping preview in the preview layer when they switch the constituent active device. Not dramatic, but compared to the other phones it looks like a glitch.

Thank you very much!

Kind regards, Sebastian

import UIKit
import AVFoundation

class ViewController: UIViewController {
    
    var captureSession : AVCaptureSession!
    var captureDevice : AVCaptureDevice!
    var captureInput : AVCaptureInput!
    var previewLayer : AVCaptureVideoPreviewLayer!
    var activePrimaryConstituentToken: NSKeyValueObservation?
    var zoomToken: NSKeyValueObservation?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        checkPermissions()
        setupAndStartCaptureSession()
    }
    
    func checkPermissions() {
        let cameraAuthStatus =  AVCaptureDevice.authorizationStatus(for: AVMediaType.video)
        switch cameraAuthStatus {
        case .authorized:
            return
        case .denied:
            abort()
        case .notDetermined:
            AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler:
                                            { (authorized) in
                if(!authorized){
                    abort()
                }
            })
        case .restricted:
            abort()
        @unknown default:
            fatalError()
        }
    }
    
    func setupAndStartCaptureSession() {
        DispatchQueue.global(qos: .userInitiated).async{
            
            self.captureSession = AVCaptureSession()
            self.captureSession.beginConfiguration()
            
            if self.captureSession.canSetSessionPreset(.photo) {
                self.captureSession.sessionPreset = .photo
            }
            self.captureSession.automaticallyConfiguresCaptureDeviceForWideColor = true
            
            self.setupInputs()
            
            DispatchQueue.main.async {
                self.setupPreviewLayer()
            }
            self.captureSession.commitConfiguration()
            
            self.captureSession.startRunning()
            
            self.activePrimaryConstituentToken = self.captureDevice.observe(\.activePrimaryConstituent, options: [.new], changeHandler: { (device, change) in
                let type = device.activePrimaryConstituent!.deviceType.rawValue
                print("Device type: \(type)")
            })
            self.zoomToken = self.captureDevice.observe(\.videoZoomFactor, options: [.new], changeHandler: { (device, change) in
                let zoom = device.videoZoomFactor
                print("Zoom: \(zoom)")
            })
            
            let switchZoomFactor = 2.0
            
            DispatchQueue.main.async {
                self.setZoom(CGFloat(switchZoomFactor), animated: false)
            }
        }
    }
    
    func setupInputs() {
        
        if let device = AVCaptureDevice.default(.builtInTripleCamera, for: .video, position: .back) {
            captureDevice = device
        } else {
            fatalError("no back camera")
        }
        
        guard let input = try? AVCaptureDeviceInput(device: captureDevice) else {
            fatalError("could not create input device from back camera")
        }
        
        if !captureSession.canAddInput(input) {
            fatalError("could not add back camera input to capture session")
        }
        captureInput = input
        captureSession.addInput(input)
    }
    
    func setupPreviewLayer() {
        previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        view.layer.addSublayer(previewLayer)
        previewLayer.frame = self.view.layer.frame
    }
    
    func setZoom(_ value: CGFloat, animated: Bool) {
        guard let device = captureDevice else { return }
        let maxZoom: CGFloat = captureDevice.maxAvailableVideoZoomFactor
        let minZoom: CGFloat = captureDevice.minAvailableVideoZoomFactor
        
        let zoomValue = max(min(value, maxZoom), minZoom)
        let deltaZoom = Float(abs(zoomValue - device.videoZoomFactor))
        do {
            try device.lockForConfiguration()
            if animated {
                device.ramp(toVideoZoomFactor: zoomValue, withRate: max(deltaZoom * 50.0, 50.0))
            } else {
                device.videoZoomFactor = zoomValue
            }
            device.unlockForConfiguration()
        } catch {
            return
        }
    }
}
Answered by DTS Engineer in 818806022

Hey @SebastianHu_Scanbot,

If you have not already, please file a bug report for this issue using Feedback Assistant.

-- Greg

Hey @SebastianHu_Scanbot,

Thanks for the focused code, I'll be looking into this, stay tuned :)

Greg

Much appreciated. Thank you, Greg. Let me know if I can assist with further information or testing.

Hey @SebastianHu_Scanbot,

If you have not already, please file a bug report for this issue using Feedback Assistant.

-- Greg

Hey Greg,

filed a bug report as requested: https://feedbackassistant.apple.com/feedback/16130284

Greetings and thank you!

Constituent active device switching very slow on iPhone 16 Pro Models on focus changes
 
 
Q