2

I'm trying to implement a camera preview view in SwiftUI, for which I have the following code:

import SwiftUI
import AVFoundation

struct CameraPreview: UIViewRepresentable {
    let session: AVCaptureSession
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        view.backgroundColor = .gray

        let videoPreviewLayer = AVCaptureVideoPreviewLayer(session: session)
        videoPreviewLayer.frame = view.bounds
        videoPreviewLayer.videoGravity = .resizeAspectFill
        videoPreviewLayer.connection?.videoOrientation = .portrait

        view.layer.addSublayer(videoPreviewLayer)

        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        for layer in uiView.layer.sublayers ?? [] {
            layer.frame = uiView.bounds
        }
    }
}

However I do see the gray background view that I set on the view, but it never starts showing the camera output. I've set a AVCaptureVideoDataOutputSampleBufferDelegate class and I can see the frames being captured and processed, yet for some reason it does not start rendering the output.

I have this other snippet that DOES render the output, but it does so by setting the preview layer as the root layer which is what I want to avoid, here's the code that works:

struct CameraPreview: UIViewRepresentable {
    let session: AVCaptureSession
    
    func makeUIView(context: Context) -> UIView {
        let view = VideoView()
        view.backgroundColor = .gray

        view.previewLayer.session = session
        view.previewLayer.videoGravity = .resizeAspectFill
        view.previewLayer.connection?.videoOrientation = .portrait

        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        for layer in uiView.layer.sublayers ?? [] {
            layer.frame = uiView.bounds
        }
    }
    
    class VideoView: UIView {
        override class var layerClass: AnyClass {
            AVCaptureVideoPreviewLayer.self
        }
        
        var previewLayer: AVCaptureVideoPreviewLayer {
           layer as! AVCaptureVideoPreviewLayer
        }
    }
}

Some examples I found showed I should be able to show the preview like I do in the first example. I've tried initializing the session with inputs before and after the preview view is created and I've gotten the same result. Am I missing anything? am I not retaining the layer or is there a special configuration for the session to look out for? to make it work I simply swap the implementations and the one with the inner class does render?

Any help is really appreciated.

Some resources:

Tristian
  • 3,406
  • 6
  • 29
  • 47
  • Have you tried setting the `zPosition` of the layer to something >0? – Frank Rupprecht Dec 02 '20 at 06:47
  • Also, I actually like the second approach (with the `VideoView`) more since you would actually not need to update the layer's frame in `updateUIView`. The layer should always resize together with the view. – Frank Rupprecht Dec 02 '20 at 06:49
  • @FrankSchlegel I just did, same result. I can actually remove the code inside of `updateUIView` and it has no effect on both snippets. – Tristian Dec 02 '20 at 08:17

0 Answers0