iosswifterror-handlingcamera

iOS App crashing due to Camera permission


I have a view controller where I'm trying to open a camera to scan a QR code. Every time I open the VC it crashes my app with an error message:

This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data.

I have double-checked my info.plist and in Target as well. I have permitted the camera usage already, but still facing the same error every time. What is the missing dot? Here is my VC code:

enter image description here

import UIKit


import AVFoundation

class Scanner__VC: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
var captureSession: AVCaptureSession!
var previewLayer: AVCaptureVideoPreviewLayer!




override func viewDidLoad() {
    super.viewDidLoad()

    
    view.backgroundColor = UIColor.black
            captureSession = AVCaptureSession()

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

    do {
        videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
        
    } catch {
                return
            }

    if (captureSession.canAddInput(videoInput)) {
        captureSession.addInput(videoInput)
        
    } else {
        failed()
        return
        
    }

            let metadataOutput = AVCaptureMetadataOutput()

    if (captureSession.canAddOutput(metadataOutput)) {
        captureSession.addOutput(metadataOutput)
        
        metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
        metadataOutput.metadataObjectTypes = [.qr]
        
    } else {
        failed()
        return
        
    }

    previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
    previewLayer.frame = view.layer.bounds
    previewLayer.videoGravity = .resizeAspectFill
    view.layer.addSublayer(previewLayer)

    captureSession.startRunning()
    
}


func failed() {
    let ac = UIAlertController(title: "Scanning not supported", message: "Your device does not support scanning a code from an item. Please use a device with a camera.", preferredStyle: .alert)
    ac.addAction(UIAlertAction(title: "OK", style: .default))
    present(ac, animated: true)
    captureSession = nil
    
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    
    if (captureSession?.isRunning == false) {
        captureSession.startRunning()
    }
    
    
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    if (captureSession?.isRunning == true) {
        captureSession.stopRunning()
    }
    
}

func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
    captureSession.stopRunning()
    
    if let metadataObject = metadataObjects.first {
        guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
        guard let stringValue = readableObject.stringValue else { return }
        AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
        found(code: stringValue)
    }
    
    dismiss(animated: true)
    
}

func found(code: String) {
    print(code)
    
}

override var prefersStatusBarHidden: Bool {
    return true
    
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .portrait
    
}


func showAlert(with message: String) {
    let alert = UIAlertController(title: "QR Code Scanned", message: message, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default))
    present(alert, animated: true)
    
}

Solution

  • You have:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>UIApplicationSceneManifest</key>
        <dict>
            <key>NSCameraUsageDescription</key>
            <string>Camera usage reason</string>
            <key>UIApplicationSupportsMultipleScenes</key>
            <false/>
            <key>UISceneConfigurations</key>
            <dict>
                <key>UIWindowSceneSessionRoleApplication</key>
                <array>
                    <dict>
                        <key>UISceneConfigurationName</key>
                        <string>Default Configuration</string>
                        <key>UISceneDelegateClassName</key>
                        <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
                        <key>UISceneStoryboardFile</key>
                        <string>Main</string>
                    </dict>
                </array>
            </dict>
        </dict>
    </dict>
    </plist>
    
    

    For the next use of NSCameraUsageDescription in my answer, it can be applied to the PhotoLibrary & the two Location permissions too.

    NSCameraUsageDescription is inside UIApplicationSceneManifest (in a hierarchy level speaking), while it should be at the top hierarchy of the plist, on the same level as UIApplicationSceneManifest.

    You should have something like that:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>NSCameraUsageDescription</key>
        <string>Camera usage reason</string>
        <key>UIApplicationSceneManifest</key>
        <dict>
            <key>UIApplicationSupportsMultipleScenes</key>
            <false/>
            <key>UISceneConfigurations</key>
            <dict>
                <key>UIWindowSceneSessionRoleApplication</key>
                <array>
                    <dict>
                        <key>UISceneConfigurationName</key>
                        <string>Default Configuration</string>
                        <key>UISceneDelegateClassName</key>
                        <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
                        <key>UISceneStoryboardFile</key>
                        <string>Main</string>
                    </dict>
                </array>
            </dict>
        </dict>
    </dict>
    </plist>
    
    

    I put it before the UIApplicationSceneManifest to highlight the fact that they are at the same hierarchy level, but it could be afterward, it doesn't matter, it's a key access (dictionary), not index access (array)