1// Copyright 2016-present 650 Industries. All rights reserved. 2 3#import <EXBarCodeScanner/EXBarCodeScanner.h> 4#import <EXBarCodeScanner/EXBarCodeScannerUtils.h> 5#import <UMBarCodeScannerInterface/UMBarCodeScannerInterface.h> 6#import <UMCore/UMDefines.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 UM_WEAKIFY(self); 45 [self _runBlockIfQueueIsPresent:^{ 46 UM_ENSURE_STRONGIFY(self); 47 [self maybeStartBarCodeScanning]; 48 }]; 49 } 50 } 51 } 52} 53 54- (void)setIsEnabled:(BOOL)newBarCodeScanning 55{ 56 if ([self isScanningBarCodes] == newBarCodeScanning) { 57 return; 58 } 59 _barCodesScanning = newBarCodeScanning; 60 UM_WEAKIFY(self); 61 [self _runBlockIfQueueIsPresent:^{ 62 UM_ENSURE_STRONGIFY(self); 63 if ([self isScanningBarCodes]) { 64 if (self.metadataOutput) { 65 [self _setConnectionsEnabled:true]; 66 } else { 67 [self maybeStartBarCodeScanning]; 68 } 69 } else { 70 [self _setConnectionsEnabled:false]; 71 } 72 }]; 73} 74 75# pragma mark - Public API 76 77- (void)maybeStartBarCodeScanning 78{ 79 if (!_session || !_sessionQueue || ![self isScanningBarCodes]) { 80 return; 81 } 82 83 if (!_metadataOutput) { 84 [_session beginConfiguration]; 85 86 AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init]; 87 [metadataOutput setMetadataObjectsDelegate:self queue:_sessionQueue]; 88 if ([_session canAddOutput:metadataOutput]) { 89 [_session addOutput:metadataOutput]; 90 _metadataOutput = metadataOutput; 91 } 92 [_session commitConfiguration]; 93 94 if (!_metadataOutput) { 95 return; 96 } 97 } 98 99 NSArray<AVMetadataObjectType> *availableRequestedObjectTypes = @[]; 100 NSArray<AVMetadataObjectType> *requestedObjectTypes = @[]; 101 NSArray<AVMetadataObjectType> *availableObjectTypes = _metadataOutput.availableMetadataObjectTypes; 102 if (_settings && _settings[EX_BARCODE_TYPES_KEY]) { 103 requestedObjectTypes = [[NSArray alloc] initWithArray:_settings[EX_BARCODE_TYPES_KEY]]; 104 } 105 106 for(AVMetadataObjectType objectType in requestedObjectTypes) { 107 if ([availableObjectTypes containsObject:objectType]) { 108 availableRequestedObjectTypes = [availableRequestedObjectTypes arrayByAddingObject:objectType]; 109 } 110 } 111 112 [_metadataOutput setMetadataObjectTypes:availableRequestedObjectTypes]; 113} 114 115- (void)stopBarCodeScanning 116{ 117 if (!_session) { 118 return; 119 } 120 121 [_session beginConfiguration]; 122 123 if ([_session.outputs containsObject:_metadataOutput]) { 124 [_session removeOutput:_metadataOutput]; 125 _metadataOutput = nil; 126 } 127 128 [_session commitConfiguration]; 129 130 if ([self isScanningBarCodes] && _onBarCodeScanned) { 131 _onBarCodeScanned(nil); 132 } 133} 134 135# pragma mark - Private API 136 137- (void)_setConnectionsEnabled:(BOOL)enabled 138{ 139 if (!_metadataOutput) { 140 return; 141 } 142 for (AVCaptureConnection *connection in _metadataOutput.connections) { 143 connection.enabled = enabled; 144 } 145} 146 147- (void)_runBlockIfQueueIsPresent:(void (^)(void))block 148{ 149 if (_sessionQueue) { 150 dispatch_async(_sessionQueue, block); 151 } 152} 153 154# pragma mark - AVCaptureMetadataOutputObjectsDelegate 155 156- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects 157 fromConnection:(AVCaptureConnection *)connection 158{ 159 if (!_settings || !_settings[EX_BARCODE_TYPES_KEY] || !_metadataOutput) { 160 return; 161 } 162 163 for(AVMetadataObject *metadata in metadataObjects) { 164 if([metadata isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) { 165 AVMetadataMachineReadableCodeObject *codeMetadata = (AVMetadataMachineReadableCodeObject *) metadata; 166 for (id barcodeType in _settings[EX_BARCODE_TYPES_KEY]) { 167 if ([metadata.type isEqualToString:barcodeType]) { 168 169 NSDictionary *event = @{ 170 @"type" : codeMetadata.type, 171 @"data" : codeMetadata.stringValue 172 }; 173 174 if (_onBarCodeScanned) { 175 _onBarCodeScanned(event); 176 } 177 return; 178 } 179 } 180 } 181 } 182} 183 184# pragma mark - default settings 185 186+ (NSDictionary *)_getDefaultSettings 187{ 188 return @{ 189 EX_BARCODE_TYPES_KEY: [[EXBarCodeScannerUtils validBarCodeTypes] allValues], 190 }; 191} 192 193@end 194