I’m working on an iOS project that required finding all of the users available cameras on their device. This used to be simple enough prior to iOS 10, as you could just call:
AVCaptureDevice.devices()
This would retrieve all available cameras on the users device. Sadly, thats been deprecated.
The confusing world of virtual devices
With the introduction of a wide variety of cameras in iOS devices, Apple now combines certain focal lengths into one virtual camera type. For example, if you’re looking to get all the cameras on the back of a modern iPhone [insert number here] Pro, you could just search for the device type below, using the DiscoverySession
on AVCaptureDevice
:
let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInTripleCamera]
mediaType: AVMediaType.video,
position: .back)
let foundCameras = deviceDiscoverySession.devices
Of course, this only works if you know the exact device the user is on, and could prove very tedious having to check for all the known iPhone/iPad types and match them to their camera type in AVFoundation
.
It also gets even more cumbersome if you want to find a specific lens within that camera system. You have the ability to use DiscoverySession
with both a single cameras lens, and virtual cameras. So say you want to find the wide-angle lens on the back of an iPhone 15 Pro, both of the below have the potential to work, so which do you choose?
let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera]
mediaType: AVMediaType.video,
position: .back)
let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInTripleCamera]
mediaType: AVMediaType.video,
position: .back)
What we are able to do is to prod the discovery session to search for multiple device types, on multiple locations on the device (i.e front or back). And because the DiscoverySession
will return the devices in priority order, it means we’re able to also include the single camera systems, like the one on the iPad Air for example, and the OS will determine that it should return the camera systems with multiple cameras closer to the top of the array.
Switching between lens’ on a virtual device
If you opt for returning a virtual device with multiple cameras, you have the ability to switch between those cameras seamlessly by setting the zoom factor.
Doing the below on the triple camera system on the iPhone 15 Pro, would switch you to the ultra-wide lens:
let _ = try? device.lockForConfiguration()
device.videoZoomFactor = 1.0
device.unlockForConfiguration()
The Solution
To find all available cameras on modern iOS device, we need to use the discovery session to look for all possible cameras on a modern iOS device. This ends up looking like the below:
let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera,
.builtInDualWideCamera,
.builtInTripleCamera,
.builtInWideAngleCamera,
.builtInTelephotoCamera,
.builtInUltraWideCamera],
mediaType: AVMediaType.video,
position: .unspecified)
let foundCameras = deviceDiscoverySession.devices
Virtual camera systems with more than one lens are returned near the top of the array, and you’re able to switch cameras in that camera system by adjusting the zoom factor.
Stitching it all together
Using all of the above, if I wanted to get all the cameras on a device, and I wanted to be able to select the wide-angle back camera from that array of devices, the code I’d end up using might look something like this:
func getWideAngleCamera() -> AVCaptureDevice? {
// Start a discovery session to find all available cameras on your device.
let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera,
.builtInDualWideCamera,
.builtInTripleCamera,
.builtInWideAngleCamera,
.builtInTelephotoCamera,
.builtInUltraWideCamera],
mediaType: AVMediaType.video,
position: .unspecified)
// Get the first camera at the back. This may either be a single camera lens, or a camera system
// depending on the device you're using.
if let camera = deviceDiscoverySession.devices.first(where: { $0.position == .back }) {
// If the camera is a single lens system, then we can return the wide angle camera thats been
// found (.builtInWideAngleCamera). If the camera is a type of `.builtInDualCamera` camera system
// (iPhone X class devices), then we can also return the camera as by default the wide angle lens is selected.
if camera.deviceType == .builtInDualCamera || camera.deviceType == .builtInWideAngleCamera {
return camera
}
// If its none of the above devices, we can use `virtualDeviceSwitchOverVideoZoomFactors` to find
// out what where the zoom factors are where the virtual camera system switches over lens'.
// By default, the camera systen will start using the lowest zoom factor (0.5x, i.e the ultra-wide lens),
// so the wide-angle lens will be the first in the `virtualDeviceSwitchOverVideoZoomFactors` array.
if let wideAngleZoom = camera.virtualDeviceSwitchOverVideoZoomFactors.first {
let _ = try? camera.lockForConfiguration()
camera.videoZoomFactor = CGFloat(truncating: wideAngleZoom)
camera.unlockForConfiguration()
}
}
return nil
}