11

In previous versions of swift, you create a AVCaptureVideoPreviewLayer in a ViewController and add it to the default view using view.layer.addSublayer(previewLayer).

How is this done in SwiftUI ContentView? None of the View types in SwiftUI appear to have an addSublayer. There is no Text("Hello World").layer.addSublayer....

I have tried adding previewLayer to various views in ContentView

import Foundation
import AVFoundation
import Combine
import SwiftUI

class Scanner: NSObject, AVCaptureMetadataOutputObjectsDelegate, ObservableObject {

    @Published var captureSession: AVCaptureSession!
    @Published var previewLayer: AVCaptureVideoPreviewLayer!
    @Published var previewView: UIView

    override init() {

        captureSession = AVCaptureSession()
        previewLayer = nil
        //previewView = UIView()

        super.init()

        guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
        let videoInput: AVCaptureDeviceInput

        do {
            videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
        } catch {
            return
        }

        if (captureSession.canAddInput(videoInput)) {
            captureSession.addInput(videoInput)
        } else {
            failed()
            return
        }

        let metadataOutput = AVCaptureMetadataOutput()

        if (captureSession.canAddOutput(metadataOutput)) {
            captureSession.addOutput(metadataOutput)

            metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            metadataOutput.metadataObjectTypes = [.qr]
        } else {
            failed()
            return
        }

        previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.videoGravity = .resizeAspectFill

        //previewView.layer.addSublayer(previewLayer)

    }

import SwiftUI
import Combine


struct ContentView: View {

    @ObservedObject var scanner = Scanner()

    var body: some View {

        //Text("Hello World").layer.addSublayer(scanner.previewLayer)
        //Text("")
        Text("HelloWorld")//.addSublayer(scanner.previewLayer))

            //.previewLayout(scanner.previewLayer)
            .layer.addSublayer(scanner.previewLayer)

            //.previewLayout(scanner.previewLayer)

            //.overlay(scanner.previewView)

        scanner.captureSession.startRunning()

    }
}

Compile errors trying to add previewLayer

Tim
  • 597
  • 8
  • 18

2 Answers2

6

You can't add a layer directly. That why people currently bottle up the whole thing inside UIView(Controller)Representable like many other things.

Fabian
  • 5,040
  • 2
  • 23
  • 35
  • 6
    Peder, I had to recreate it and it is very basic...Here you go: https://github.com/owingst/SwiftUIScanner – Tim Nov 03 '19 at 21:15
4

I manage to put the captured image onto the SwiftUI view

In the view component, simply put a

Image(uiImage: cameraManager.capturedImage)

And for CameraManager, using frame capture function, after transforming the sample buffer to UIImage, simply set the capturedImage to the uiImage. (Referring to https://medium.com/ios-os-x-development/ios-camera-frames-extraction-d2c0f80ed05a)

class CameraManager: NSObject, ObservableObject{
    ...
    @Published public var capturedImage: UIImage = UIImage()
    ...
}

extension CameraManager: AVCaptureVideoDataOutputSampleBufferDelegate {

    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

        // transforming sample buffer to UIImage
        guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
        let ciImage = CIImage(cvPixelBuffer: imageBuffer)
        let context = CIContext()
        guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return }
        let uiImage = UIImage(cgImage: cgImage)
        
        // publishing changes to the main thread
        DispatchQueue.main.async {
            self.capturedImage = uiImage
        }
    }

}

I was intended to capture every frame for later image processing, although it can preview now, I'm not sure about by adding a computer vision algorithm, will it affect the frame capturing. Till now, feel running the model will be on another thread, so...

Well, open to comment

  • 1
    This is not very efficient. See my comment: https://stackoverflow.com/q/74908056/1971013 – meaning-matters Jan 04 '23 at 08:33
  • The only issue with this approach is memory. When you are capturing 4K, the image size is huge, and you will pass and present it in view. By nature of SwiftUI, it will update the view each time a new image comes. Now imagine how much size it needed for 3840x2160. If you add an extra filter layer to each frame by using `CIImage` and `applyingFilter` it will cause some lags in the preview and consume a lot of memory. But by using `AVCaptureVideoPreviewLayer` we will not have this problem even if we want to preview 4k. – Sajjad Sarkoobi Jan 13 '23 at 08:33