Categories
english

Reading Barcodes on iOS 7

iOS 7 introduced APIs to read bar codes using the camera. I could have easily overlooked it without this excellent post from doubleencore.

NSHipster provided sample code, but it missed some details to work. Here is a sample which does.

Sample code

@import AVFoundation;

@interface CEViewController () <AVCaptureMetadataOutputObjectsDelegate>

@property (strong) AVCaptureSession *captureSession;

@end

@implementation CEViewController

- (void)viewDidLoad
{
	[super viewDidLoad];

	self.captureSession = [[AVCaptureSession alloc] init];
	AVCaptureDevice *videoCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
	NSError *error = nil;
	AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoCaptureDevice error:&error];
	if(videoInput)
		[self.captureSession addInput:videoInput];
	else
		NSLog(@"Error: %@", error);

	AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
	[self.captureSession addOutput:metadataOutput];
	[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
	[metadataOutput setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code]];

	AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
	previewLayer.frame = self.view.layer.bounds;
	[self.view.layer addSublayer:previewLayer];

	[self.captureSession startRunning];
}

#pragma mark AVCaptureMetadataOutputObjectsDelegate

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
	for(AVMetadataObject *metadataObject in metadataObjects)
	{
		AVMetadataMachineReadableCodeObject *readableObject = (AVMetadataMachineReadableCodeObject *)metadataObject;
		if([metadataObject.type isEqualToString:AVMetadataObjectTypeQRCode])
		{
			NSLog(@"QR Code = %@", readableObject.stringValue);
		}
		else if ([metadataObject.type isEqualToString:AVMetadataObjectTypeEAN13Code])
		{
			NSLog(@"EAN 13 = %@", readableObject.stringValue);
		}
	}
}

@end

Pitfalls

The issue I had with NSHipster’s sample code is the delegate method was not called at all. I quickly understood this was because the AVCaptureMetadataOutput must be configured to tell which metadata to recognise.

But, and this was not obvious to me, -[AVCaptureMetadataOuput setMetadataObjectTypes:] must be called after -[AVCaptureSession addOutput:]. Otherwise, the following message shows in the console:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[AVCaptureMetadataOutput setMetadataObjectTypes:] - unsupported type found.  Use -availableMetadataObjectTypes.'

I did try to look at the output of -availableMetadataObjectTypes, and it returns an empty array.

Therefore, -addOuput: must be called before setMetadataObjectTypes. In retrospect, it makes sense: the output object must know it is linked to a video session to know which metadata it may provide.