With iOS7, Apple introduced AVCaptureMetadataOutputObjects
, which is used for scanning barcodes. If you check out the web for how to scan barcodes in iOS, almost everyone is talking about ZBarSDK. And before iOS7, this was definitely the way to go. But as of iOS7, ZBarSDK has a pretty nasty memory leak, and it's a big library to include when you consider barcode scanning functionality is already built into iOS7. Moreover, I consider Apple's approach to be a slightly easier than ZBarSDK was anyway.
But I decided to make it even easier. I'm working on a wrapper class that will turn several lines of code into just a few lines.
NHGBarcodeScanner.h
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
typedef NS_OPTIONS(unsigned short, BarcodeType) {
CODE_UPCE = 1 << 0,
CODE_CODE39 = 1 << 1,
CODE_CODE39MOD43 = 1 << 2,
CODE_EAN13 = 1 << 3,
CODE_EAN8 = 1 << 4,
CODE_CODE93 = 1 << 5,
CODE_CODE128 = 1 << 6,
CODE_PDF417 = 1 << 7,
CODE_QRCODE = 1 << 8,
CODE_AZTEC = 1 << 9
};
@protocol NHGBarcodeScannerDelegate <NSObject>
@required - (void)barcodeScannerDidScan:(NSString*)result;
@optional - (void)barcodeInitDidFailWithError:(NSError*)error;
@end
@interface NHGBarcodeScanner : UIView
@property (nonatomic,assign) id<NHGBarcodeScannerDelegate> delegate;
- (id)init __attribute__((unavailable()));
- (id)initWithFrame:(CGRect)frame __attribute__((unavailable()));
- (id)initWithFrame:(CGRect)frame barcodeTypes:(BarcodeType)barcodeTypes
delegate:(id<NHGBarcodeScannerDelegate>)delegate;
+ (instancetype)nhgBarcodeScannerWithFrame:(CGRect)frame
barcodeTypes:(BarcodeType)barcodeTypes
delegate:(id<NHGBarcodeScannerDelegate>)delegate;
- (void)startScanning;
@end
NHGBarcodeScanner.m
#import "NHGBarcodeScanner.h"
@interface NHGBarcodeScanner() <AVCaptureMetadataOutputObjectsDelegate>
@end
@implementation NHGBarcodeScanner {
AVCaptureSession *_captureSession;
NSArray *_barcodeTypes;
}
+ (instancetype)nhgBarcodeScannerWithFrame:(CGRect)frame
barcodeTypes:(BarcodeType)barcodeTypes
delegate:(id<NHGBarcodeScannerDelegate>)delegate {
return [[self alloc] initWithFrame:frame barcodeTypes:barcodeTypes delegate:delegate];
}
- (id)initWithFrame:(CGRect)frame
barcodeTypes:(BarcodeType)barcodeTypes
delegate:(id<NHGBarcodeScannerDelegate>)delegate {
self = [super initWithFrame:frame];
if (self) {
_delegate = delegate;
_captureSession = [[AVCaptureSession alloc] init];
AVCaptureDevice *videoCaptureDevice = [AVCaptureDevice
defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput
deviceInputWithDevice:videoCaptureDevice error:&error];
if(videoInput) {
[_captureSession addInput:videoInput];
} else {
#if DEBUG
NSLog(@"barcodeInitDidFailWithError: %@", error);
#endif
if ([_delegate respondsToSelector:@selector(barcodeInitDidFailWithError:)]) {
[_delegate barcodeInitDidFailWithError:error];
}
return nil;
}
AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
[_captureSession addOutput:metadataOutput];
[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
_barcodeTypes = [self metadataObjectTypesForOptions:barcodeTypes];
[metadataOutput setMetadataObjectTypes:_barcodeTypes];
[metadataOutput setRectOfInterest:frame];
AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer
alloc] initWithSession:_captureSession];
previewLayer.frame = self.frame;
[self.layer addSublayer:previewLayer];
}
return self;
}
- (NSArray*)metadataObjectTypesForOptions:(BarcodeType)barcodeTypes {
NSMutableArray *metadataObjectTypes = [NSMutableArray array];
if (barcodeTypes & CODE_UPCE) {
[metadataObjectTypes addObject:AVMetadataObjectTypeUPCECode];
}
if (barcodeTypes & CODE_CODE39) {
[metadataObjectTypes addObject:AVMetadataObjectTypeCode39Code];
}
if (barcodeTypes & CODE_CODE39MOD43) {
[metadataObjectTypes addObject:AVMetadataObjectTypeCode39Mod43Code];
}
if (barcodeTypes & CODE_EAN13) {
[metadataObjectTypes addObject:AVMetadataObjectTypeEAN13Code];
}
if (barcodeTypes & CODE_EAN8) {
[metadataObjectTypes addObject:AVMetadataObjectTypeEAN8Code];
}
if (barcodeTypes & CODE_CODE93) {
[metadataObjectTypes addObject:AVMetadataObjectTypeCode93Code];
}
if (barcodeTypes & CODE_CODE128) {
[metadataObjectTypes addObject:AVMetadataObjectTypeCode128Code];
}
if (barcodeTypes & CODE_PDF417) {
[metadataObjectTypes addObject:AVMetadataObjectTypePDF417Code];
}
if (barcodeTypes & CODE_QRCODE) {
[metadataObjectTypes addObject:AVMetadataObjectTypeQRCode];
}
if (barcodeTypes & CODE_AZTEC) {
[metadataObjectTypes addObject:AVMetadataObjectTypeAztecCode];
}
return [NSArray arrayWithArray:metadataObjectTypes];
}
- (void)startScanning {
[_captureSession startRunning];
}
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection {
[_captureSession stopRunning];
for (AVMetadataObject *metadataObject in metadataObjects) {
AVMetadataMachineReadableCodeObject *readableObject =
(AVMetadataMachineReadableCodeObject *)metadataObject;
for (NSString *barcodeType in _barcodeTypes) {
if ([barcodeType isEqualToString:metadataObject.type]) {
[self.delegate barcodeScannerDidScan:readableObject.stringValue];
return;
}
}
}
}
- (void)removeFromSuperview {
[_captureSession stopRunning];
[super removeFromSuperview];
}
@end
Example usage:
NHGBarcodeScanner *scanner = [NHGBarcodeScanner nhgBarcodeScannerWithFrame:
someFrame barcodeTypes:(CODE_UPCE | CODE_EAN13 | CODE_CODE128) delegate:self];
[someParentView addSubview:scanner];
[scanner startScanning];
This will add a scanner to someParentView
that will scan for UPCE, EAN13, and CODE128 barcodes.
My primary concern with the existing code is the metadataObjectTypesForOptions:
method. I don't know if there's a better way to handle this bit mask.
I'm also curious if anyone feels like this class is missing anything.