swiftswiftuiavfoundationuiviewcontrollerrepresentable

Rotation of UIViewRepresentable View using AVFoundation


I am using CameraPreview (UIViewRepresentable) as a bridge between UIKit and SwiftUI. CameraPreview is configured with CameraModel and uses AVCaptureVideoPreviewLayer. CameraModel is configuring AVCaptureSession, and AVCaptureDeviceInput. Everything works fine, except when I try to rotate device on iPad. The CameraPreview is not rotated properly and uses input from previous rotation. (Pictures below) I can check the device being rotated in SwiftUI (based on this article) , but I do not know how to reconfigure rotation ofCameraPreview and AVCaptureVideoPreviewLayer . Any Idea how to trigger layout update in UIKit after I find out that device was rotated?

Code below:

struct CameraView: View {
    @StateObject var camera = CameraModel()

    var body: some View {
            CameraPreview(camera: camera)
                .ignoresSafeArea(.all, edges: .all)
     }
}

struct CameraPreview: UIViewRepresentable {
    @ObservedObject var camera: CameraModel
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView(frame: UIScreen.main.bounds)
        camera.preview = AVCaptureVideoPreviewLayer(session: camera.session)
        camera.preview.frame = view.frame
        
        //your own properies
        camera.preview.videoGravity = .resizeAspectFill
        view.layer.addSublayer(camera.preview)
        
        //starting session
        camera.session.startRunning()
        return view
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) { }
}

class CameraModel: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate {
    @Published var session = AVCaptureSession()
    @Published var preview: AVCaptureVideoPreviewLayer!
    @Published var output = AVCapturePhotoOutput()

    func setUp() {
        do {
            self.session.beginConfiguration()
            let device = AVCaptureDevice.default(for: .video)
            let input = try AVCaptureDeviceInput(device:device!)
            if self.session.canAddInput(input) {
                self.session.addInput(input)
            } 
            if self.session.canAddOutput(self.output) {
                self.session.addOutput(self.output)
            } 
            self.session.commitConfiguration()
            
        }catch {
            print(error.localizedDescription)
        }
    }
}

picture before rotation picture after rotation


Solution

  • I solved the issue by following:

    1. adapting preview frame to bounds instead of frame.

    2. when I detect change of device orientation on some superview View, I change the value of deviceRotation variable, which automatically triggers updateUIView function.

       struct CameraPreview: UIViewRepresentable {
         @ObservedObject var camera: CameraModel
         var deviceRotation: UIDeviceOrientation
      
        func makeUIView(context: Context) -> UIView {
           let view = UIView(frame: UIScreen.main.bounds)
           camera.preview = AVCaptureVideoPreviewLayer(session: camera.session)
           camera.preview.frame = view.bounds
      
           //your own properies
           camera.preview.videoGravity = .resizeAspectFill
           view.layer.addSublayer(camera.preview)
      
           //starting session
           camera.session.startRunning()
           return view
        }
      
        func updateUIView(_ uiView: UIViewType, context: Context) {
           let view = UIView(frame: UIScreen.main.bounds)
           camera.preview.frame = view.bounds
        }  
      }
      
    3. manually setting AVCaptureVideoOrientation on AVCaptureConnection to correct device orientation. I do this every time when i detect orientation change:

      previewConnection.videoPreviewLayer?.connection?.videoOrientation = orientation.videoOrientation