5
\$\begingroup\$

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.

asked Feb 20, 2014 at 3:23
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

One possible improvement for the sake of convenience could be to add some meta groups to the enum.

For example:

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
}
typedef NS_OPTIONS(unsigned short, BarcodeGroup) {
 CODEGROUP_1DBarcodes = 1 << 0
 + 1 << 1
 + 1 << 2
 + 1 << 3
 + 1 << 4
 + 1 << 5
 + 1 << 6
 CODEGROUP_2DBarcodes = 1 << 7
 + 1 << 8
 + 1 << 9
 CODEGROUP_ALLBarcodes = 1 << 0
 + 1 << 1
 + 1 << 2
 + 1 << 3
 + 1 << 4
 + 1 << 5
 + 1 << 6
 + 1 << 7
 + 1 << 8
 + 1 << 9
};

Now instead of combining large groups with the |, a user could instead simply call one of these code groups if they needed all, or just 1d codes or just 2d codes.


What's also potentially missing is a way to grab multiple codes in a batch. AVCaptureMetadataOutput will capture as many barcodes as you put in front of it all at once. It puts all these symbols into an array, and then your forin loops in the delegate method grab a single one of these codes.

There are two potential ways of sending the multiple codes.

  1. The delegate method (barcodeScannerDidScan:) could take an NSArray argument, rather than a single NSString. Internally, captureOutput:didOutputMetadataObjects:fromConnection: would then just need a single forin loop to check that the symbol matches the requested symbols, and use this loop to build an array. After the array is built, send it to the delegate.
  2. The return; can be removed from the nested forin loops. The result is the delegate method will be called once for each barcode that was scanned.
answered Feb 20, 2014 at 3:44
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.