📑
AVFoundationのカメラをSwiftUIで使用する
2種類の方法を試した
- UIViewControllerを自作して UIViewControllerRepresentableして使用する
- previewViewをUIViewRepresentableして使用する
なお、AVFoundationのカメラセッションの設定は公式Documentationからコピペした。
以下2種類のコードはカメラセッションを開始し、セッション中に得られるプレビュー画面を表示するだけのコードである。写真撮影や動画録画機能は加えていない。
UIViewControllerを自作してUIViewControllerRepresentableして使用する
import AVFoundation
import UIKit
import SwiftUI
// UIViewControllerの定義
class CameraUIViewController: UIViewController {
// カメラセッションをclassプロパティとして定義
let captureSession = AVCaptureSession()
override func viewDidLoad() {
super.viewDidLoad()
captureSession.beginConfiguration()
connectInputsToSession()
connectOutputToSession()
captureSession.commitConfiguration()
let previewView = PreviewView()
previewView.videoPreviewLayer.session = self.captureSession
view.addSubview(previewView)
previewView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
previewView.topAnchor.constraint(equalTo: view.topAnchor),
previewView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
previewView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
previewView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
DispatchQueue.global(qos: .background).async {
self.captureSession.startRunning()
}
}
// Input設定
private func connectInputsToSession() {
let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .unspecified)
guard let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice!), captureSession.canAddInput(videoDeviceInput)
else {
print("error")
return
}
captureSession.addInput(videoDeviceInput)
}
// Output設定
private func connectOutputToSession() {
let photoOutput = AVCapturePhotoOutput()
guard captureSession.canAddOutput(photoOutput)
else {
print("error")
return
}
captureSession.sessionPreset = .photo
captureSession.addOutput(photoOutput)
}
// preview用画面UIView
class PreviewView: UIView {
override class var layerClass: AnyClass {
return AVCaptureVideoPreviewLayer.self
}
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
}
}
// CameraUIViewControllerをSwiftUIのViewに変換
struct CameraUIViewControllerRepresentable: UIViewControllerRepresentable {
typealias UIViewControllerType = CameraUIViewController
func makeUIViewController(context: Context) -> UIViewControllerType {
return CameraUIViewController()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
// Canvasでプレビュー(カメラが接続できないのでクラッシュする)
struct CameraUIViewControllerRepresentablePreview: PreviewProvider {
static var previews: some View {
CameraUIViewControllerRepresentable()
}
}
以上で下記部分についてはChatGPTに記述してもらった。この部分がないと実機でリアルタイムのカメラpreview画面が表示されない。
view.addSubview(previewView)
previewView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
previewView.topAnchor.constraint(equalTo: view.topAnchor),
previewView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
previewView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
previewView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
previewViewをUIViewRepresentableして使用する
import SwiftUI
import AVFoundation
// Viewの定義
struct CameraView: View {
// カメラセッションをclassプロパティとして定義
let captureSession = AVCaptureSession()
var body: some View {
PreviewViewUIView(captureSession: captureSession)
.onAppear(perform: {setupCamera()})
}
func setupCamera() {
captureSession.beginConfiguration()
connectInputsToSession()
connectOutputToSession()
captureSession.commitConfiguration()
DispatchQueue.global(qos: .background).async {
self.captureSession.startRunning()
}
}
// Input設定
private func connectInputsToSession() {
let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .unspecified)
guard let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice!), captureSession.canAddInput(videoDeviceInput)
else {
print("error")
return
}
captureSession.addInput(videoDeviceInput)
}
// Output設定
private func connectOutputToSession() {
let photoOutput = AVCapturePhotoOutput()
guard captureSession.canAddOutput(photoOutput)
else {
print("error")
return
}
captureSession.sessionPreset = .photo
captureSession.addOutput(photoOutput)
}
// preview用画面UIView
class PreviewView: UIView {
override class var layerClass: AnyClass {
return AVCaptureVideoPreviewLayer.self
}
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
}
// PreviewViewをUIViewからSwiftUI用Viewに変換
struct PreviewViewUIView: UIViewRepresentable {
typealias UIViewControllerType = PreviewView
let captureSession: AVCaptureSession
func makeUIView(context: Context) -> UIViewControllerType {
let previewView = PreviewView()
previewView.videoPreviewLayer.session = captureSession
return previewView
}
func updateUIView(_ uiViewController: UIViewControllerType, context: Context) {
}
}
}
// Canvasでプレビュー(カメラが接続できないのでクラッシュする)
struct CameraView_Previews: PreviewProvider {
static var previews: some View {
CameraView()
}
}
共通で行ったこと
公式Documentationの
self.captureSession.startRunning()
の部分は
DispatchQueue.global(qos: .background).async {
self.captureSession.startRunning()
}
としてメインスレッドを避けた。避けないと紫色の警告が出る。
まとめ
UIViewControllerRepresentableはUIViewControllerを、UIViewRepresentableはUIViewを対象としてSwiftUI用Viewに変換するので変換範囲がかなり異なることがわかる。SwiftUIで学び始めた人はpreviewViewをUIViewRepresentableして使用するのがおすすめ。
Discussion