I would like to have a camera view show up in sheet view to be able to scan barcodes. For some reason, the camera layer created doesn't want to appear in the sheet view, even though the green dot appears on the iPhone, or the logs say that everything is fine.
// MainView.swift
@State private var showScanSheet = false
var body: some View {
NavigationStack {
VStack {
...
}.sheet(isPresented: $showScanSheet) {
ScannerView()
}
}
}
// ScannerView.swift
import SwiftUI
import AVKit
struct ScannerView: View {
@State private var isScanning: Bool = false
@State private var session: AVCaptureSession = .init()
@State private var cameraPermission: Permission = .idle
@State private var barcodeOutput: AVCaptureMetadataOutput = .init()
@State private var errorMessage: String = ""
@State private var showError: Bool = false
@Environment(\.openURL) private var openURL
@StateObject private var barcodeDelegate = BarcodeScannerDelegate()
var body: some View {
GeometryReader {
let size = $0.size
ZStack {
CameraView(frameSize: CGSize(width: size.width, height: 200), session: $session).scaleEffect(0.97)
RoundedRectangle(cornerRadius: 10, style: .circular)
.trim(from: 0.55, to: 0.60)
.stroke(Color.red, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))
.padding()
RoundedRectangle(cornerRadius: 10, style: .circular)
.trim(from: 0.55, to: 0.60)
.stroke(Color.red, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))
.rotationEffect(.init(degrees: 180))
.padding()
RoundedRectangle(cornerRadius: 10, style: .circular)
.trim(from: 0.40, to: 0.45)
.stroke(Color.red, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))
.padding()
RoundedRectangle(cornerRadius: 10, style: .circular)
.trim(from: 0.40, to: 0.45)
.stroke(Color.red, style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))
.rotationEffect(.init(degrees: 180))
.padding()
}
.frame(width: size.width, height: 200)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.onAppear(perform: checkCameraPermission)
.alert(errorMessage, isPresented: $showError) {
if cameraPermission == .denied {
Button("Settings") {
let settingsString = UIApplication.openSettingsURLString
if let settingsURL = URL(string: settingsString) {
openURL(settingsURL)
}
}
Button("Cancel", role: .cancel) {}
}
}
}
func checkCameraPermission() {
print("Checking camera permission")
Task {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized:
cameraPermission = .approved
setupCamera()
case .notDetermined, .denied, .restricted:
if await AVCaptureDevice.requestAccess(for: .video) {
cameraPermission = .approved
setupCamera()
} else {
cameraPermission = .denied
presentError("Please provide access to the camera for scanning barcodes.")
}
default: break
}
print(cameraPermission)
}
}
func setupCamera() {
do {
guard let device = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInUltraWideCamera], mediaType: .video, position: .back).devices.first else {
presentError("Unknown error.")
return
}
let input = try AVCaptureDeviceInput(device: device)
guard session.canAddInput(input), session.canAddOutput(barcodeOutput) else {
presentError("Unknown error.")
return
}
session.beginConfiguration()
session.addInput(input)
session.addOutput(barcodeOutput)
barcodeOutput.metadataObjectTypes = [.upce, .ean8, .ean13]
barcodeOutput.setMetadataObjectsDelegate(barcodeDelegate, queue: .main)
session.commitConfiguration()
DispatchQueue.global(qos: .background).async {
session.startRunning()
}
} catch {
presentError(error.localizedDescription)
}
}
func presentError(_ message: String) {
errorMessage = message
showError.toggle()
}
}
// BarcodeScannerDelegate.swift
import Foundation
import AVKit
class BarcodeScannerDelegate: NSObject, ObservableObject, AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
if let metadataObject = metadataObjects.first {
guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
guard let scannedCode = readableObject.stringValue else { return }
print(scannedCode)
}
}
}
// CameraView.swift
import SwiftUI
import AVKit
struct CameraView: UIViewRepresentable {
var frameSize: CGSize
@Binding var session: AVCaptureSession
func makeUIView(context: Context) -> UIView {
let view = UIViewType(frame: CGRect(origin: .zero, size: frameSize))
view.backgroundColor = .clear
let cameraLayer = AVCaptureVideoPreviewLayer(session: session)
cameraLayer.frame = .init(origin: .zero, size: frameSize)
cameraLayer.videoGravity = .resizeAspectFill
cameraLayer.masksToBounds = true
view.layer.addSublayer(cameraLayer)
return view
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
However, the app works completely as expected when instead of using a sheet, I use a fullScreenCover or a navigationDestination in MainView.swift.
when using with fullScreenCover
Thank you for your help.