[SwiftUI][AVFoundation] AVCaptureVideoPreviewView をSwiftUI から利用する方法

SwiftUI&AVFoundation

UIImagePickerController を使うと簡単に画像を取り込むことができるのですが、常に用意されたビューを使用しないといけないことが難点です。

自分で作ったビューの中にプレビューを表示するための方法を解説します。

使用するクラス

UIImagePickerController は、内部で色々とやってくれていたのですが、自分でビューをコントロールしようとすると、以下のようなクラスを使うことになります。

AVCaptureSession
AVFoundation を使う上でポイントとなるクラスです。このクラスに対して、入力や出力を設定することとなります。
AVCaptureDevice/AVCaptureDeviceInput
その名の通り、入力デバイスを表すクラスです。例えば、前面カメラだったり、背面カメラだったりです。
AVCaptureOutput/AVCaptureDeviceOutput
その名の通り、出力デバイスを表すクラスです。画像だったり、ビデオだったりを扱うことになります。

以下で、各クラスを少しづつ説明します。

AVCaptureSession

ビデオや写真を処理する上でポイントとなるクラスです。

Apple のドキュメントは、こちら

入力デバイスから受け取ったデータを処理して、出力デバイスに渡すことを処理してくれます。

言い方を変えると、入力と出力を設定しないと何もしてくれません。

以下のような関数を使って、入出力を設定します。

  • func addInput(_ input: AVCaptureInput)
  • func addOutput(_ output: AVCaptureOutput)

上記以外に、canAddInput/canAddOutput で接続可能かを確認したり、removeInput/removeOutput で設定した入出力を止めることもできます。

AVCaptureDevice/AVCaptureDeviceInput

AVCaptureDevice がデバイスを表す抽象クラスで、AVCaptureDeviceInput は、入力デバイスを抽象化しています。

Appleのドキュメントは、こちら

以下のような形で、AVCaptureDeviceInput を用意して、AVCaptureSesssion に設定します。

コード

    var captureSession: AVCaptureSession!

    func setupSession() {
        captureSession = AVCaptureSession()
        captureSession.beginConfiguration()

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

        guard let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice) else { return }
        guard captureSession.canAddInput(videoInput) else { return }
        captureSession.addInput(videoInput)

        /* snip */
    }

AVCaptureOutput/AVCaptureDeviceOutput

Apple のドキュメントは、こちら

AVCaptureOutput がデバイスを表す抽象クラスで、AVCaptureDeviceOutput は、出力デバイスを抽象化しています。

以下のような形で、AVCaptureOutput を用意して、AVCaptureSession に設定します。

コード

    var captureSession: AVCaptureSession!

    func setupSession() {
        captureSession = AVCaptureSession()
        captureSession.beginConfiguration()

        /* snip */

        let photoOutput = AVCapturePhotoOutput()
        guard captureSession.canAddOutput(photoOutput) else { return }
        captureSession.sessionPreset = .photo
        captureSession.addOutput(photoOutput)
    }

AVCaptureSession の設定まとめ

まとめると、以下のようなコードで、AVCaptureSession の入出力を設定することになります。

コード

    var captureSession: AVCaptureSession!

    func setupSession() {
        captureSession = AVCaptureSession()
        captureSession.beginConfiguration()
        guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }

        guard let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice) else { return }
        guard captureSession.canAddInput(videoInput) else { return }
        captureSession.addInput(videoInput)

        let photoOutput = AVCapturePhotoOutput()
        guard captureSession.canAddOutput(photoOutput) else { return }
        captureSession.sessionPreset = .photo
        captureSession.addOutput(photoOutput)
        
        captureSession.commitConfiguration()  
    }

ここまでのコードで基本的な処理はできるのですが、通常、プレビューが必要となります。

プレビューの方法

プレビューのために、AVCaptureVideoPreviewLayer というクラスが用意されています。そのなの通り、CALayer の子クラスです。

Apple のドキュメントは、こちら

このクラスの初期化時に、AVCaptureSession を渡すと、そこで処理されているものをプレビューできるようになります。

AVCaptureVidePreviewLayer の生成

以下のようなコードで、対象となる AVCaptureSession を設定したAVCaptureVideoPreviewLayer を生成することができます。

コード

        let previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
        self.layer.addSublayer(previewLayer) // # 表示させるためには、何らかのUIViewにレイヤーとして追加する必要があります。

AVCaptureSession の稼働

AVCaptureSession は、設定しただけでは動き始めません。.startRunning() をコールして、明示的に動作を開始させる必要があります。

SwiftUI で使うために、もう1手間必要

プレビューも含めて使うためには、CALayer を扱う必要があり、そのままでは、SwiftUI からは、直接使うことができません。

例によって、UIViewRepresentable で Wrap した UIView を使うことが必要となります。

SwiftUIAVCaptureVideoPreviewView と UIAVCaptureVideoPreviewView

public class UIAVCaptureVideoPreviewView: UIView {
    var captureSession: AVCaptureSession!

    func setupSession() {
        captureSession = AVCaptureSession()
        captureSession.beginConfiguration()
        guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }

        guard let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice) else { return }
        guard captureSession.canAddInput(videoInput) else { return }
        captureSession.addInput(videoInput)

        let photoOutput = AVCapturePhotoOutput()
        guard captureSession.canAddOutput(photoOutput) else { return }
        captureSession.sessionPreset = .photo
        captureSession.addOutput(photoOutput)
        
        captureSession.commitConfiguration()
    }
    
    func setupPreview() {
        self.frame = CGRect(x: 0, y: 0, width: 500, height: 500)   // 空のUIViewを使っているため、適当なサイズ設定が必要です

        let previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
        previewLayer.frame = self.frame
        
        self.layer.addSublayer(previewLayer)

        self.captureSession.startRunning()
    }
}

public struct SwiftUIAVCaptureVideoPreviewView: UIViewRepresentable {

    public func makeUIView(context: Context) -> UIAVCaptureVideoPreviewView {
        let view = UIAVCaptureVideoPreviewView()
        view.setupSession()
        view.setupPreview()
        return view
    }
    
    public func updateUIView(_ uiView: UIAVCaptureVideoPreviewView, context: Context) {
    }
}

この SwiftUIAVCaptureVideoPreviewView は、以下のように使います。

コード

struct ContentView: View {
    var body: some View {
        VStack {
                SwiftUIAVCaptureVideoPreviewView()
        }
    }
}

まとめ:動画/画像のプレビューを SwiftUI で表示するためには

以下の手順が必要となります。

  • AVFoundation を使って、AVCaptureSession を設定する
  • AVCaptureVideoPreviewLayer を使って、プレビューするためのビューを作る
  • UIViewRepresentable を使って、プレビューするためのビューを SwiftUI から利用できるようにする

AVFoundation を使ったプレビューを 表示するための方法を説明しました。さらに、そのビューを SwiftUI から利用する方法も併せて説明しました。

説明は以上です。

1 COMMENT

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です