We have a camera application which is attempting to display a zoomed in area of the AVCaptureVideoPreviewLayer
from an AVCaptureDeviceInput
, while still capturing images of the non-zoomed in area of the chosen camera. The user also needs to be able to tap to focus.
We are currently using the below SwiftUI component to achieve this. However, when handling the tap focus events using videoPreviewLayer.captureDevicePointConverted
with videoPreviewCropFactor
set to 2.0 (effectively zooming in the camera preview by 2x), the captureDevicePoint
do not correspond with the correct positions in the capture device reference frame.
import AVFoundation
import SwiftUI
@available(iOS 15.0, *)
struct CameraPreview: UIViewRepresentable {
var preview: AVCaptureVideoPreviewLayer
var cropFactor: Double
func makeUIView(context _: Context) -> UIView {
let uiView = UIView()
uiView.layer.addSublayer(self.preview)
DispatchQueue.main.async {
self.preview.bounds = self.getPreviewBounds(uiView)
self.preview.position = uiView.layer.position
}
return uiView
}
func updateUIView(_ uiView: UIView, context _: Context) {
self.preview.bounds = self.getPreviewBounds(uiView)
self.preview.position = uiView.layer.position
}
func getPreviewBounds(_ uiView: UIView) -> CGRect {
return CGRect(x: 0, y: 0,
width: uiView.layer.bounds.width * self.cropFactor,
height: uiView.layer.bounds.height * self.cropFactor)
}
}
// parent component
struct YieldMultiCaptureView: View {
var body: some View {
ZStack {
CameraPreview(preview: cameraController.videoPreviewLayer, cropFactor: self.videoPreviewCropFactor)
.ignoresSafeArea()
.opacity(cameraController.isCaptureInProgress ? 0.2 : 1)
.onTouch(type: .started, perform: onCameraViewTap)
...
func onCameraViewTap(_ location: CGPoint) {
let captureDevicePoint = cameraController.videoPreviewLayer.captureDevicePointConverted(fromLayerPoint: location)
cameraController.focusCamera(focusPoint: captureDevicePoint)
}
...
Note: previous answers I have seen suggest adding a zoom factor to the device input. However, this will not work in this case since we still want to capture the non-zoomed region of the camera.