CameraView not displaying when used with a sheet view in SwiftUI

49 Views Asked by At

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) {
        
    }
}

When using with .sheet

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.

0

There are 0 best solutions below