Search code examples
iosswiftbarcodefreezescanning

After a barcode scan the screen freezes


I am learning SWIFT and getting better. The barcode application I have works, but it keeps scanning the barcode endlessly, until I force a pop up screen 'Ok', 'Cancel' to accept or reject the scan. On research, I found very similar code (shown below). So I created a test project with just one view controller to try this code. It works great, but freezes as soon as the barcode is scanned. My intent is UNFREEZE the screen, accept and save this and allow to user to scan more barcodes. Any help is greatly appreciated. I tried stop scanning just under the line found(code:) but it didn't help unfreezing. I may be wrong, but I suspect the viewilldisapper never gets called here.


import AVFoundation
import UIKit

class ScannerViewController: 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 = [.ean8, .ean13, .pdf417]
    } 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
}

}


Edited code:

 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)

        barcodeValue = stringValue
    }

    // dismiss(animated: true)
    previewLayer.removeFromSuperlayer()

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

    let alertController = UIAlertController(title: "Barcode Scanned", message: barcodeValue!, preferredStyle: .alert)
    alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: {(alert: UIAlertAction!) in self.restartScan()}))
    alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler:nil))
    present(alertController, animated: true, completion: nil)
    //captureSession.startRunning()
}

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

func restartScan() {
 captureSession.startRunning()
}

Solution

  • It's not a freeze , when the first barcode is detected you stop the process with captureSession.stopRunning() inside func metadataOutput(_ output: AVCaptureMetadataOutput you need this

    previewLayer.removeFromSuperlayer()
    

    instead of this

    dismiss(animated: true)
    

    as it's a layer not a vc to dismiss that you previously added in

    view.layer.addSublayer(previewLayer)
    

    Note: background of your view is black