SwiftUI에서 카메라(AVFoundation)을 사용하려면 어떻게 해야할지 알아봅시다
실제 기기가 필요합니다 (시뮬레이터는 안돼요)
AVFoundation으로 카메라를 설정&사용하는 View를 생성합니다
UIViewRepresentable를 통해 SwiftUI에서 불러옵니다
간단한 기본화면을 만들어보겠습니다
버튼 하나를 둬서 전면/후면이 전환되도록 하겠습니다
struct ContentView: View {
var body: some View {
VStack {
Spacer()
Spacer()
HStack {
Spacer()
Spacer()
Button(action: {print("Hello")}) {
Image(systemName: "arrow.triangle.2.circlepath.camera")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
}.padding(.trailing, 20)
}
}
}
}
레이어를 AVCaptureVideoPreviewLayer로 가지고 있는 UIView를 만들어 줍니다
AVCaptureSession에서 나온 AVCaptureVideoDataOutput이 바로 이 레이어로 연결됩니다
class PreviewView:UIView {
override class var layerClass: AnyClass {
return AVCaptureVideoPreviewLayer.self
}
var previewLayer: AVCaptureVideoPreviewLayer? {
return layer as? AVCaptureVideoPreviewLayer
}
}
PreviewView를 SwiftUI로 바꿔줍니다
PreviewView를 생성하면서 카메라 설정도 해줍니다
카메라 제어할 serial queue를 생성합니다
private let cameraQueue = DispatchQueue(label: "cameraQueue")
카메라의 input과 output을 관리하는 객체입니다
private var captureSession = AVCaptureSession()
카메라로부터 받아온 데이터를 사용할 곳을 지정합니다
let videoOutput = AVCaptureVideoDataOutput()
self.captureSession.addOutput(videoOutput)
어떤 카메라 하드웨어를 사용할 지 설정합니다
AVCaptureDevice.DiscoverySession를 통해 질의하여 조건에 맞는 하드웨어를 탐색합니다
앞면, 뒷면 카메라를 모두 사용하기 때문에 카메라 위치는 변수로 받아줍니다
mutating func configureCamera(cameraPosition: AVCaptureDevice.Position) {
let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInUltraWideCamera], mediaType: .video, position: cameraPosition)
guard let cameraDevice = discoverySession.devices.first else {
fatalError("no camera device is available")
}
//...
}
생성한 AVCaptureDevice를 기반으로 AVCaptureDeviceInput을 생성합니다
mutating func configureCamera(cameraPosition: AVCaptureDevice.Position) {
//...
do {
if let currentInput = currentInput { // 카메라 전환시 기존 연결 해제
self.captureSession.removeInput(currentInput)
}
let deviceInput = try AVCaptureDeviceInput(device: cameraDevice)
self.captureSession.addInput(deviceInput)
self.currentInput = deviceInput
} catch {
print("error = \(error.localizedDescription)")
}
}
카메라 기능을 사용하려면 사용자에게 허락을 받아야 합니다
func requestCameraPermission() {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { authStatus in
if authStatus {
self.cameraQueue.async {
self.captureSession.startRunning() // 카메라 시작
}
}
}
case .restricted:
break
case .authorized:
self.cameraQueue.async {
self.captureSession.startRunning() // 카메라 시작
}
default:
print("Permession declined")
}
}
key: Privacy - Camera Usage Description (100% 똑같아야 합니다)
Value: Use camera (마음대로 입력해도 됩니다)
ContentView에서 cameraPreviewView를 사용합니다
import SwiftUI
import AVFoundation
struct ContentView: View {
@State var cameraPosition: AVCaptureDevice.Position = .back
@State var cameraPreviewView: CameraPreviewView
var body: some View {
ZStack {
cameraPreviewView
.ignoresSafeArea()
.onAppear(perform: {
cameraPreviewView.requestCameraPermission()
})
VStack {
Spacer()
Spacer()
HStack {
Spacer()
Spacer()
Button(action: {
let newPosition = cameraPosition == .back ? AVCaptureDevice.Position.front : .back
cameraPosition = newPosition
cameraPreviewView.configureCamera(cameraPosition: newPosition)
}) {
Image(systemName: "arrow.triangle.2.circlepath.camera")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
}.padding(.trailing, 20)
}
}
}
}
}
import SwiftUI
import AVFoundation
struct ContentView: View {
@State var cameraPosition: AVCaptureDevice.Position = .back
@State var cameraPreviewView: CameraPreviewView
var body: some View {
ZStack {
cameraPreviewView
.ignoresSafeArea()
.onAppear(perform: {
cameraPreviewView.requestCameraPermission()
})
VStack {
Spacer()
Spacer()
HStack {
Spacer()
Spacer()
Button(action: {
let newPosition = cameraPosition == .back ? AVCaptureDevice.Position.front : .back
cameraPosition = newPosition
cameraPreviewView.configureCamera(cameraPosition: newPosition)
}) {
Image(systemName: "arrow.triangle.2.circlepath.camera")
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
}.padding(.trailing, 20)
}
}
}
}
}
import Foundation
import AVFoundation
import SwiftUI
struct CameraPreviewView: UIViewRepresentable {
init(cameraPosition: AVCaptureDevice.Position) {
configureCaptureSession(cameraPosition: cameraPosition)
}
func makeUIView(context: Context) -> UIView {
let view = PreviewView()
view.backgroundColor = .black
if let previewLayer = view.previewLayer {
previewLayer.session = captureSession
previewLayer.videoGravity = .resizeAspectFill
previewLayer.connection?.videoRotationAngle = 90.0
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
// do nothing
}
func requestCameraPermission() {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { authStatus in
if authStatus {
self.cameraQueue.async {
self.captureSession.startRunning()
}
}
}
case .restricted:
break
case .authorized:
self.cameraQueue.async {
self.captureSession.startRunning()
}
default:
print("Permession declined")
}
}
private let cameraQueue = DispatchQueue(label: "cameraQueue")
private var captureSession = AVCaptureSession()
private mutating func configureCaptureSession(cameraPosition: AVCaptureDevice.Position) {
let videoOutput = AVCaptureVideoDataOutput()
self.captureSession.addOutput(videoOutput)
configureCamera(cameraPosition: cameraPosition)
}
private var currentInput: AVCaptureDeviceInput?
mutating func configureCamera(cameraPosition: AVCaptureDevice.Position) {
let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera,.builtInUltraWideCamera], mediaType: .video, position: cameraPosition)
guard let cameraDevice = discoverySession.devices.first else {
fatalError("no camera device is available")
}
do {
if let currentInput = currentInput {
self.captureSession.removeInput(currentInput)
}
let deviceInput = try AVCaptureDeviceInput(device: cameraDevice)
self.captureSession.addInput(deviceInput)
self.currentInput = deviceInput
} catch {
print("error = \(error.localizedDescription)")
}
}
}
fileprivate class PreviewView:UIView {
override class var layerClass: AnyClass {
return AVCaptureVideoPreviewLayer.self
}
var previewLayer: AVCaptureVideoPreviewLayer? {
return layer as? AVCaptureVideoPreviewLayer
}
}
전면까지 찍어야 하다보니 마땅한 곳이 없네요...ㅎ
https://ios-development.tistory.com/1043
https://enebin.medium.com/swiftui만-써서-호다닥-카메라앱-만들기-feat-mvvm-1-2782b457f796
카메라 출력부를 레이어만 뜯어 화면에 띄울 수 있는걸 처음 알았어요..! 항상 뷰컨을 래핑해 스유에서 띄웠었는데 간단한 화면정도라면 uiview로부터 가져오면 되는군요!! 오늘도 하나 배워갑니다..ㅎㅎㅎ