1// Copyright 2016-present 650 Industries. All rights reserved. 2 3#import <EXBarCodeScanner/EXBarCodeScanner.h> 4#import <EXBarCodeScanner/EXBarCodeScannerUtils.h> 5#import <EXBarCodeScannerInterface/EXBarCodeScannerInterface.h> 6#import <EXCore/EXDefines.h> 7 8@interface EXBarCodeScanner() <AVCaptureMetadataOutputObjectsDelegate> 9 10@property (nonatomic, strong) AVCaptureMetadataOutput *metadataOutput; 11@property (nonatomic, weak) AVCaptureSession *session; 12@property (nonatomic, weak) dispatch_queue_t sessionQueue; 13@property (nonatomic, copy, nullable) void (^onBarCodeScanned)(NSDictionary*); 14@property (nonatomic, assign, getter=isScanningBarCodes) BOOL barCodesScanning; 15@property (nonatomic, strong) NSDictionary<NSString *, id> *settings; 16 17@end 18 19NSString *const EX_BARCODE_TYPES_KEY = @"barCodeTypes"; 20 21@implementation EXBarCodeScanner 22 23- (instancetype)init 24{ 25 if (self = [super init]) { 26 _settings = [[NSMutableDictionary alloc] initWithDictionary:[[self class] _getDefaultSettings]]; 27 } 28 return self; 29} 30 31# pragma mark - JS properties setters 32 33- (void)setSettings:(NSDictionary<NSString *, id> *)settings 34{ 35 for (NSString *key in settings) { 36 if ([key isEqualToString:EX_BARCODE_TYPES_KEY]) { 37 NSArray<NSString *> *value = settings[key]; 38 NSSet *previousTypes = [NSSet setWithArray:_settings[EX_BARCODE_TYPES_KEY]]; 39 NSSet *newTypes = [NSSet setWithArray:value]; 40 if (![previousTypes isEqualToSet:newTypes]) { 41 NSMutableDictionary<NSString *, id> *nextSettings = [[NSMutableDictionary alloc] initWithDictionary:_settings]; 42 nextSettings[EX_BARCODE_TYPES_KEY] = value; 43 _settings = nextSettings; 44 [self maybeStartBarCodeScanning]; 45 } 46 } 47 } 48} 49 50- (void)setIsEnabled:(BOOL)newBarCodeScanning 51{ 52 if ([self isScanningBarCodes] == newBarCodeScanning) { 53 return; 54 } 55 _barCodesScanning = newBarCodeScanning; 56 EX_WEAKIFY(self); 57 [self _runBlockIfQueueIsPresent:^{ 58 EX_ENSURE_STRONGIFY(self); 59 if ([self isScanningBarCodes]) { 60 if (self.metadataOutput) { 61 [self _setConnectionsEnabled:true]; 62 } else { 63 [self maybeStartBarCodeScanning]; 64 } 65 } else { 66 [self _setConnectionsEnabled:false]; 67 } 68 }]; 69} 70 71# pragma mark - Public API 72 73- (void)maybeStartBarCodeScanning 74{ 75 if (!_session || !_sessionQueue || ![self isScanningBarCodes]) { 76 return; 77 } 78 79 if (!_metadataOutput) { 80 [_session beginConfiguration]; 81 82 AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init]; 83 [metadataOutput setMetadataObjectsDelegate:self queue:_sessionQueue]; 84 if ([_session canAddOutput:metadataOutput]) { 85 [_session addOutput:metadataOutput]; 86 _metadataOutput = metadataOutput; 87 } 88 [_session commitConfiguration]; 89 90 if (!_metadataOutput) { 91 return; 92 } 93 } 94 95 NSArray<AVMetadataObjectType> *availableRequestedObjectTypes = @[]; 96 NSArray<AVMetadataObjectType> *requestedObjectTypes = @[]; 97 NSArray<AVMetadataObjectType> *availableObjectTypes = _metadataOutput.availableMetadataObjectTypes; 98 if (_settings && _settings[EX_BARCODE_TYPES_KEY]) { 99 requestedObjectTypes = [[NSArray alloc] initWithArray:_settings[EX_BARCODE_TYPES_KEY]]; 100 } 101 102 for(AVMetadataObjectType objectType in requestedObjectTypes) { 103 if ([availableObjectTypes containsObject:objectType]) { 104 availableRequestedObjectTypes = [availableRequestedObjectTypes arrayByAddingObject:objectType]; 105 } 106 } 107 108 [_metadataOutput setMetadataObjectTypes:availableRequestedObjectTypes]; 109} 110 111- (void)stopBarCodeScanning 112{ 113 if (!_session) { 114 return; 115 } 116 117 [_session beginConfiguration]; 118 119 if ([_session.outputs containsObject:_metadataOutput]) { 120 [_session removeOutput:_metadataOutput]; 121 _metadataOutput = nil; 122 } 123 124 [_session commitConfiguration]; 125 126 if ([self isScanningBarCodes] && _onBarCodeScanned) { 127 _onBarCodeScanned(nil); 128 } 129} 130 131# pragma mark - Private API 132 133- (void)_setConnectionsEnabled:(BOOL)enabled 134{ 135 if (!_metadataOutput) { 136 return; 137 } 138 for (AVCaptureConnection *connection in _metadataOutput.connections) { 139 connection.enabled = enabled; 140 } 141} 142 143- (void)_runBlockIfQueueIsPresent:(void (^)(void))block 144{ 145 if (_sessionQueue) { 146 dispatch_async(_sessionQueue, block); 147 } 148} 149 150# pragma mark - AVCaptureMetadataOutputObjectsDelegate 151 152- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects 153 fromConnection:(AVCaptureConnection *)connection 154{ 155 if (!_settings || !_settings[EX_BARCODE_TYPES_KEY] || !_metadataOutput) { 156 return; 157 } 158 159 for(AVMetadataObject *metadata in metadataObjects) { 160 if([metadata isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) { 161 AVMetadataMachineReadableCodeObject *codeMetadata = (AVMetadataMachineReadableCodeObject *) metadata; 162 for (id barcodeType in _settings[EX_BARCODE_TYPES_KEY]) { 163 if ([metadata.type isEqualToString:barcodeType]) { 164 165 NSDictionary *event = @{ 166 @"type" : codeMetadata.type, 167 @"data" : codeMetadata.stringValue 168 }; 169 170 if (_onBarCodeScanned) { 171 _onBarCodeScanned(event); 172 } 173 return; 174 } 175 } 176 } 177 } 178} 179 180# pragma mark - default settings 181 182+ (NSDictionary *)_getDefaultSettings 183{ 184 return @{ 185 EX_BARCODE_TYPES_KEY: [[EXBarCodeScannerUtils validBarCodeTypes] allValues], 186 }; 187} 188 189@end 190