Barcode scanning in iOS using AVFoundation

Scanning barcodes in smartphone using it’s camera is as old as smartphones themselves. In olden days (like a couple of years in smartphone terms :)), it was really a PITA to implement scanning of barcodes in your iPhone/iPad application. But not anymore.

Thanks to Apple! The AVFoundation framework now allows you to scan barcodes with fewer lines of code without adding another library to your project. In the blog we will see how we can make you of this feature.

The framework reads the below barcodes:
2D Barcodes:

  • QR
  • PDF417
  • Aztec

1D Barcodes:

  • Code 128
  • EAN-8
  • EAN-13
  • UPC-A
  • UPC-E
  • Code 39
  • Code 93

It is fairly simple to implement. So I will jump right into the implementation.

1. Initialising a AVCaptureSession:

An AVCaptureSession co-ordinates the input device and the output device.
You can initialise a capture session as below.

self.captureSession = [[AVCaptureSession alloc] init];

2. Specifying Input for Capture session:

Input can be video, audio etc. In this case we will initialise the default device which in most cases is the primary camera and the media type as video.

AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];

Now set the defined input device for the session as below. Suitable error handling needs to be done if the input device did not initialise.

[self.captureSession addInput:input];

3. Specifying Output for Capture session:

An metadata output capture object needs to be initialised and a queue is specified for notifying the delegate.

AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
[self.captureSession addOutput:captureMetadataOutput];
// Create a new queue and set delegate for metadata objects scanned.
dispatch_queue_t dispatchQueue;
dispatchQueue = dispatch_queue_create("scanQueue", NULL);
[captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatchQueue];
// Delegate should implement captureOutput:didOutputMetadataObjects:fromConnection: to get callbacks on detected metadata.
[captureMetadataOutput setMetadataObjectTypes:[captureMetadataOutput availableMetadataObjectTypes]];

Description:
BarcodeScanning1
4. Adding camera view to screen:

 AVCaptureVideoPreviewLayer is what displays the camera onto your screen. So initialise it and ser the attributes. Video Gravity is the property that defines how the screen should be filled and whether the aspect ratio should be preserved or not. Add the AVCaptureVideoPreviewLayer instance to the view on which you would like to display the camera.

self.captureLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
[self.captureLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[self.captureLayer setFrame:self.cameraPreviewView.layer.bounds];
[self.cameraPreviewView.layer addSublayer:self.captureLayer];

5. Reading the scanned barcode:

The delegate will get callbacks on the below method whenever a barcode is recognised. This will give you an array of the recognised scans as an AVMetadataObject. From which we will have to derive the AVMetadataMachineReadableCodeObject to read out the barcode as string.

AVMetadataMachineReadableCodeObject
The AVMetadataMachineReadableCodeObject class is a concrete subclass of AVMetadataObject defining the features of a detected one-dimensional or two-dimensional barcode.

An AVMetadataMachineReadableCodeObject instance represents a single detected machine readable code in an image. It is an immutable object describing the features and payload of a barcode.

On supported platforms, the AVCaptureMetadataOutput class outputs arrays of detected machine readable code objects.

– Apple AVFoundation Framework Reference: AVMetadataMachineReadableCodeObject

* Make sure your class corresponds to the AVCaptureMetadataOutputObjectsDelegate protocol *

Description:

BarcodeScanning2

Below is the sample implementation of this method.

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
    // Do your action on barcode capture here:
    NSString *capturedBarcode = nil;
    
    // Specify the barcodes you want to read here:
    NSArray *supportedBarcodeTypes = @[AVMetadataObjectTypeUPCECode,
                                       AVMetadataObjectTypeCode39Code,
                                       AVMetadataObjectTypeCode39Mod43Code,
                                       AVMetadataObjectTypeEAN13Code,
                                       AVMetadataObjectTypeEAN8Code,
                                       AVMetadataObjectTypeCode93Code,
                                       AVMetadataObjectTypeCode128Code,
                                       AVMetadataObjectTypePDF417Code,
                                       AVMetadataObjectTypeQRCode,
                                       AVMetadataObjectTypeAztecCode];
    
    // In all scanned values..
    for (AVMetadataObject *barcodeMetadata in metadataObjects) {
        // ..check if it is a suported barcode
        for (NSString *supportedBarcode in supportedBarcodeTypes) {
            
            if ([supportedBarcode isEqualToString:barcodeMetadata.type]) {
                // This is a supported barcode
                // Note barcodeMetadata is of type AVMetadataObject
                // AND barcodeObject is of type AVMetadataMachineReadableCodeObject
                AVMetadataMachineReadableCodeObject *barcodeObject = (AVMetadataMachineReadableCodeObject *)[self.captureLayer transformedMetadataObjectForMetadataObject:barcodeMetadata];
                capturedBarcode = [barcodeObject stringValue];
                // Got the barcode. Set the text in the UI and break out of the loop.
                
                dispatch_sync(dispatch_get_main_queue(), ^{
                    [self.captureSession stopRunning];
                    self.scannedBarcode.text = capturedBarcode;
                });
                return;
            }
        }
    }
}

Thats it! And yes, here’s a sample project for you 🙂
In Swift: BarcodeScanner
In Objective C: BarcodeScanner

Online Barcode Generation:

  1. For free QR code generation: The QR Code Generator
  2. For free 1D barcode generation: Barcoding

References:

  1. Apple AVFoundation
  2. Apple AVFoundation Framework Reference
  3. Apple AVFoundation Programming Guide
  4. Apple AVCaptureSession: Apple AVCaptureSession Class Reference
  5. Apple AV Foundation iOS Machine Readable Code Detection FAQ

In the sample project, the scanned barcode is copied to the clipboard which can be used to copy/paste URLs or any text to any text field in any other application like Safari and so on. Here’s a read related to that.

Advertisements

3 thoughts on “Barcode scanning in iOS using AVFoundation

  1. Thanks for this I was able to use a lot of this to create a plugin for a cordova project – there is no free barcode scanners cordova plugins for pdf417, crazy.

    Like

  2. Nice and clean. Thanks for posting. A curious thing happened, though. After I implemented the methods in my own project, the cemeraview took over the screen! So I went back to the raw download of your code and the same thing happened! To be more clear, the cameraview’s rectangle origin is correct but the size is bigger than the screen (1000×1000, I think). I’ll keep working on it to see if I can find a place to force that view’s size back down to how it’s set in Storyboard. If you happen to know anything about what I’m talking about, please let me know.

    Like

  3. FWIW, my solution was to copy

    [self.captureLayer setFrame:self.cameraPreviewView.layer.bounds];

    from the setup method and add it to the didAppear right before the scanning starts. There is a bit of a jitter as the camera view goes from the huge size to the one I want. If I find a way to prevent that, I’ll let you know.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s