44

I'm trying to upgrade mi app to swift 4, but the barcode reader is not working.

I have isolated the barcode reader code, and still not working. The camera works but it does not detect the barcode.

The code worked just fine on swift 3 iOS 10.

This is the complete code

import AVFoundation
import UIKit

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

override func viewDidLoad() {
    super.viewDidLoad()

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

    let videoCaptureDevice = AVCaptureDevice.default(for: AVMediaType.video)
    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 = [AVMetadataObject.ObjectType.ean8, AVMetadataObject.ObjectType.ean13, AVMetadataObject.ObjectType.pdf417]
    } else {
        failed()
        return
    }

    previewLayer = AVCaptureVideoPreviewLayer(session: captureSession);
    previewLayer.frame = view.layer.bounds;
    previewLayer.videoGravity = AVLayerVideoGravity.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 captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
    captureSession.stopRunning()

    if let metadataObject = metadataObjects.first {
        let readableObject = metadataObject as! AVMetadataMachineReadableCodeObject;

        AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
        found(code: readableObject.stringValue!);
    }

    dismiss(animated: true)
}

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

override var prefersStatusBarHidden: Bool {
    return true
}

override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    return .portrait
}
}

I am using iOS 11 on my iPhone, upgraded to beta 9.

Any idea? Thank you.

nathan
  • 9,329
  • 4
  • 37
  • 51
Dx_
  • 1,566
  • 1
  • 16
  • 17
  • 2
    So it's nice to know that this problem isn't just happening to me after updating to iOS 11 and Swift 4 for my project. I have a very basic QR code reader as well in my app using an AVCaptureMetadataOutput object and the AVCaptureMetadataOutputObjectsDelegate delegate. I have verified that everything is constantly and consistently running and not interrupted. I think at this point its time to submit a bug to Apple (both of use should). Only thing that changed were the names of properties/functions in Swift 4 but nothing else. Weird that we're not getting any delegate callbacks. – Mario A Guzman Sep 03 '17 at 19:28
  • 1
    Also, looking at your code, you need to create a Serial Queue for your AVCaptureMetadataOutputObjectsDelegate callback. metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main). Instead of using the main queue, create a serial queue as a property in your view controller and use it here rather than the main queue. – Mario A Guzman Sep 03 '17 at 19:29
  • Just for reference, can be used third party https://github.com/mahendragp/MGPBarcodeScanner – Mahendra May 31 '18 at 05:22

4 Answers4

93

I figured it out but Apple didn't make it so obvious. The callback function from the delegate AVCaptureMetadataOutputObjectsDelegate has been renamed and the parameter names are different!

So, replace

func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!)

to

func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection)

My view controller is now scanning QR Codes as before after this. It has the same parameters but the first parameter name is different. Change the function and parameter names and build/run.

starball
  • 20,030
  • 7
  • 43
  • 238
Mario A Guzman
  • 3,483
  • 4
  • 27
  • 36
  • That was the solution! The function is different, thank you! – Dx_ Sep 05 '17 at 21:02
  • 3
    I submitted an enhancement request to Apple so that Xcode will give developers a heads up if a function changes like that. I spent so long not finding the solution until I got to the API and read it line by line because no way was my code wrong. And It wasn't! That's when I realized the function name was different. – Mario A Guzman Sep 05 '17 at 21:32
  • 1
    I additionally had to use a non-Main DispatchQueue when setting the metadataObjectsDelegate, in order to get the callback to occur: `output.setMetadataObjectsDelegate(self, queue: DispatchQueue.global(qos: .userInteractive))` – Andrew Bennet Sep 08 '17 at 19:04
  • @AndrewBennet Right, I mentioned this to him also in comment attached to his question above. You have to create and pass in a Serial Queue as a param. Define one globally and pass it in. – Mario A Guzman Sep 08 '17 at 19:40
  • I can confirm that the updated delegate works on iOS9 and iOS10. – Jason Moore Oct 12 '17 at 19:22
  • Just renaming the function is insufficient, you also need to change the first parameter name from captureOutput to output for it to be called. – mbonness Oct 20 '17 at 16:15
  • @mbonness - yes, that is shown in my sample code above. – Mario A Guzman Oct 24 '17 at 18:13
  • @MarioAGuzman Got it thanks. It looks like actually the method signature is changed, the first parameter type is different, in case you want to call that out in your answer. – mbonness Oct 24 '17 at 19:11
6

After changing the delegate call back :

From

func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!)

To

func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection)

I need to set all available types for metadataObjectTypes too as below-

output.metadataObjectTypes=output.availableMetadataObjectTypes
Anindya
  • 351
  • 6
  • 7
  • This answer worked for me over the accepted answer. In addition to changing the function signature, updating the line to `output.metadataObjectTypes=output.availableMetadataObjectTypes` was ultimately what allowed my scanner to return a value – btrballin May 28 '19 at 06:22
  • In iOS 13.0 and 13.1 with Xamarin I ran into an issue where I had to explicitly set the bar code types again, as the line of code would crash: output.metadataObjectTypes=output.availableMetadataObjectTypes – Ben Butzer Sep 26 '19 at 19:11
  • 1
    Add to my comment above, it was only on the iPhone 11 pro, where this happened. – Ben Butzer Sep 26 '19 at 19:25
5

After changing your code from:

func metadataOutput(captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {}

to:

func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {}

everything works again.

Taku
  • 31,927
  • 11
  • 74
  • 85
jl.medel
  • 61
  • 2
0

You can use QRCodeScanner83 to scan barcodes:

import QRCodeScanner83
import AVFoundation

...

guard let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "CodeScannerViewController") as? CodeScannerViewController else {
    return
}
vc.callbackCodeScanned = { code in
    print("SCANNED CODE: \(code)")
    vc.dismiss(animated: true, completion: nil)
}
self.present(vc, animated: true, completion: nil)

If you need custom UI, then you can nest from CodeScannerViewController and set CodeScannerViewController.delegate to receive updates of the scanner state.

Alexander Volkov
  • 7,904
  • 1
  • 47
  • 44