1eafa96e0SStanisław Chmiela// Copyright 2018-present 650 Industries. All rights reserved. 2eafa96e0SStanisław Chmiela 3eafa96e0SStanisław Chmiela#import <EXBarCodeScanner/EXBarCodeScannerView.h> 4eafa96e0SStanisław Chmiela#import <EXBarCodeScanner/EXBarCodeScanner.h> 5eafa96e0SStanisław Chmiela#import <EXBarCodeScanner/EXBarCodeScannerUtils.h> 6284d4840SŁukasz Kosmaty#import <EXBarCodeScanner/EXBarCodeCameraRequester.h> 7fb3d99c5STomasz Sapeta#import <ExpoModulesCore/EXPermissionsInterface.h> 8*314e850cSTomasz Sapeta#import <ExpoModulesCore/EXAppLifecycleService.h> 9*314e850cSTomasz Sapeta#import <ExpoModulesCore/EXUtilities.h> 10eafa96e0SStanisław Chmiela 11eafa96e0SStanisław Chmiela@interface EXBarCodeScannerView () 12eafa96e0SStanisław Chmiela 13eafa96e0SStanisław Chmiela@property (nonatomic, strong) dispatch_queue_t sessionQueue; 14eafa96e0SStanisław Chmiela@property (nonatomic, strong) AVCaptureSession *session; 15eafa96e0SStanisław Chmiela@property (nonatomic, strong) AVCaptureDeviceInput *videoCaptureDeviceInput; 16eafa96e0SStanisław Chmiela@property (nonatomic, strong) AVCaptureMetadataOutput *metadataOutput; 17eafa96e0SStanisław Chmiela@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer; 18eafa96e0SStanisław Chmiela@property (nonatomic, strong) id runtimeErrorHandlingObserver; 19eafa96e0SStanisław Chmiela@property (nonatomic, strong) EXBarCodeScanner *barCodeScanner; 20eafa96e0SStanisław Chmiela 21*314e850cSTomasz Sapeta@property (nonatomic, weak) EXModuleRegistry *moduleRegistry; 22fb3d99c5STomasz Sapeta@property (nonatomic, weak) id<EXPermissionsInterface> permissionsManager; 23*314e850cSTomasz Sapeta@property (nonatomic, weak) id<EXAppLifecycleService> lifecycleManager; 24eafa96e0SStanisław Chmiela 25eafa96e0SStanisław Chmiela@property (nonatomic, assign, getter=isSessionPaused) BOOL paused; 26eafa96e0SStanisław Chmiela 27*314e850cSTomasz Sapeta@property (nonatomic, copy) EXDirectEventBlock onCameraReady; 28*314e850cSTomasz Sapeta@property (nonatomic, copy) EXDirectEventBlock onMountError; 29*314e850cSTomasz Sapeta@property (nonatomic, copy) EXDirectEventBlock onBarCodeScanned; 30eafa96e0SStanisław Chmiela 31eafa96e0SStanisław Chmiela@end 32eafa96e0SStanisław Chmiela 33eafa96e0SStanisław Chmiela@implementation EXBarCodeScannerView 34eafa96e0SStanisław Chmiela 35*314e850cSTomasz Sapeta- (instancetype)initWithModuleRegistry:(EXModuleRegistry *)moduleRegistry 36eafa96e0SStanisław Chmiela{ 37eafa96e0SStanisław Chmiela if ((self = [super init])) { 38eafa96e0SStanisław Chmiela _presetCamera = AVCaptureDevicePositionBack; 39eafa96e0SStanisław Chmiela _moduleRegistry = moduleRegistry; 40eafa96e0SStanisław Chmiela _session = [AVCaptureSession new]; 41eafa96e0SStanisław Chmiela _sessionQueue = dispatch_queue_create("barCodeScannerQueue", DISPATCH_QUEUE_SERIAL); 42*314e850cSTomasz Sapeta _lifecycleManager = [moduleRegistry getModuleImplementingProtocol:@protocol(EXAppLifecycleListener)]; 43fb3d99c5STomasz Sapeta _permissionsManager = [moduleRegistry getModuleImplementingProtocol:@protocol(EXPermissionsInterface)]; 44eafa96e0SStanisław Chmiela _barCodeScanner = [self createBarCodeScanner]; 45eafa96e0SStanisław Chmiela 46eafa96e0SStanisław Chmiela#if !(TARGET_IPHONE_SIMULATOR) 47eafa96e0SStanisław Chmiela _previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session]; 48eafa96e0SStanisław Chmiela _previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; 49eafa96e0SStanisław Chmiela _previewLayer.needsDisplayOnBoundsChange = YES; 50bd4c8242SŁukasz Kosmaty [_barCodeScanner setPreviewLayer:_previewLayer]; 51eafa96e0SStanisław Chmiela#endif 52eafa96e0SStanisław Chmiela _paused = NO; 53eafa96e0SStanisław Chmiela 54eafa96e0SStanisław Chmiela [_lifecycleManager registerAppLifecycleListener:self]; 55eafa96e0SStanisław Chmiela [[NSNotificationCenter defaultCenter] addObserver:self 56eafa96e0SStanisław Chmiela selector:@selector(orientationChanged:) 57eafa96e0SStanisław Chmiela name:UIDeviceOrientationDidChangeNotification 58eafa96e0SStanisław Chmiela object:nil]; 59eafa96e0SStanisław Chmiela 60eafa96e0SStanisław Chmiela [self changePreviewOrientation:[UIApplication sharedApplication].statusBarOrientation]; 61eafa96e0SStanisław Chmiela [self initializeSession]; 62eafa96e0SStanisław Chmiela } 63eafa96e0SStanisław Chmiela return self; 64eafa96e0SStanisław Chmiela} 65eafa96e0SStanisław Chmiela 66eafa96e0SStanisław Chmiela# pragma mark - events 67eafa96e0SStanisław Chmiela 68eafa96e0SStanisław Chmiela- (void)onReady 69eafa96e0SStanisław Chmiela{ 70eafa96e0SStanisław Chmiela if (_onCameraReady) { 71eafa96e0SStanisław Chmiela _onCameraReady(nil); 72eafa96e0SStanisław Chmiela } 73eafa96e0SStanisław Chmiela} 74eafa96e0SStanisław Chmiela 75eafa96e0SStanisław Chmiela- (void)onMountingError:(NSDictionary *)event 76eafa96e0SStanisław Chmiela{ 77eafa96e0SStanisław Chmiela if (_onMountError) { 78eafa96e0SStanisław Chmiela _onMountError(event); 79eafa96e0SStanisław Chmiela } 80eafa96e0SStanisław Chmiela} 81eafa96e0SStanisław Chmiela 82eafa96e0SStanisław Chmiela- (void)onBarCodeScanned:(NSDictionary *)event 83eafa96e0SStanisław Chmiela{ 84eafa96e0SStanisław Chmiela if (_onBarCodeScanned) { 85eafa96e0SStanisław Chmiela _onBarCodeScanned(event); 86eafa96e0SStanisław Chmiela } 87eafa96e0SStanisław Chmiela} 88eafa96e0SStanisław Chmiela 89eafa96e0SStanisław Chmiela# pragma mark - JS properties setters 90eafa96e0SStanisław Chmiela 91eafa96e0SStanisław Chmiela- (void)setPresetCamera:(NSInteger)presetCamera 92eafa96e0SStanisław Chmiela{ 93eafa96e0SStanisław Chmiela if (_presetCamera == presetCamera) { 94eafa96e0SStanisław Chmiela return; 95eafa96e0SStanisław Chmiela } 96eafa96e0SStanisław Chmiela _presetCamera = presetCamera; 97*314e850cSTomasz Sapeta EX_WEAKIFY(self); 98eafa96e0SStanisław Chmiela dispatch_async(_sessionQueue, ^{ 99*314e850cSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 100eafa96e0SStanisław Chmiela [self initializeSession]; 101eafa96e0SStanisław Chmiela }); 102eafa96e0SStanisław Chmiela} 103eafa96e0SStanisław Chmiela 104eafa96e0SStanisław Chmiela- (void)setBarCodeTypes:(NSArray *)barCodeTypes 105eafa96e0SStanisław Chmiela{ 106eafa96e0SStanisław Chmiela _barCodeTypes = barCodeTypes; 107eafa96e0SStanisław Chmiela [_barCodeScanner setSettings:@{ 108eafa96e0SStanisław Chmiela @"barCodeTypes": barCodeTypes, 109eafa96e0SStanisław Chmiela }]; 110eafa96e0SStanisław Chmiela} 111eafa96e0SStanisław Chmiela 112eafa96e0SStanisław Chmiela# pragma mark - lifecycle 113eafa96e0SStanisław Chmiela 114eafa96e0SStanisław Chmiela- (void)layoutSubviews 115eafa96e0SStanisław Chmiela{ 116eafa96e0SStanisław Chmiela [super layoutSubviews]; 117eafa96e0SStanisław Chmiela _previewLayer.frame = self.bounds; 118eafa96e0SStanisław Chmiela [self setBackgroundColor:[UIColor blackColor]]; 119eafa96e0SStanisław Chmiela [self.layer insertSublayer:_previewLayer atIndex:0]; 120eafa96e0SStanisław Chmiela} 121eafa96e0SStanisław Chmiela 122eafa96e0SStanisław Chmiela- (void)removeFromSuperview 123eafa96e0SStanisław Chmiela{ 124eafa96e0SStanisław Chmiela [_lifecycleManager unregisterAppLifecycleListener:self]; 125eafa96e0SStanisław Chmiela [self stopSession]; 126eafa96e0SStanisław Chmiela [super removeFromSuperview]; 127eafa96e0SStanisław Chmiela [[NSNotificationCenter defaultCenter] removeObserver:self 128eafa96e0SStanisław Chmiela name:UIDeviceOrientationDidChangeNotification 129eafa96e0SStanisław Chmiela object:nil]; 130eafa96e0SStanisław Chmiela} 131eafa96e0SStanisław Chmiela 132eafa96e0SStanisław Chmiela- (void)onAppForegrounded 133eafa96e0SStanisław Chmiela{ 134eafa96e0SStanisław Chmiela if (![_session isRunning] && [self isSessionPaused]) { 135eafa96e0SStanisław Chmiela _paused = NO; 136*314e850cSTomasz Sapeta EX_WEAKIFY(self); 137eafa96e0SStanisław Chmiela dispatch_async(_sessionQueue, ^{ 138*314e850cSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 139eafa96e0SStanisław Chmiela [self.session startRunning]; 140eafa96e0SStanisław Chmiela }); 141eafa96e0SStanisław Chmiela } 142eafa96e0SStanisław Chmiela} 143eafa96e0SStanisław Chmiela 144eafa96e0SStanisław Chmiela- (void)onAppBackgrounded 145eafa96e0SStanisław Chmiela{ 146eafa96e0SStanisław Chmiela if ([_session isRunning] && ![self isSessionPaused]) { 147eafa96e0SStanisław Chmiela _paused = YES; 148*314e850cSTomasz Sapeta EX_WEAKIFY(self); 149eafa96e0SStanisław Chmiela dispatch_async(_sessionQueue, ^{ 150*314e850cSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 151eafa96e0SStanisław Chmiela [self.session stopRunning]; 152eafa96e0SStanisław Chmiela }); 153eafa96e0SStanisław Chmiela } 154eafa96e0SStanisław Chmiela} 155eafa96e0SStanisław Chmiela 156eafa96e0SStanisław Chmiela# pragma mark - orientation 157eafa96e0SStanisław Chmiela 158eafa96e0SStanisław Chmiela- (void)orientationChanged:(NSNotification *)notification 159eafa96e0SStanisław Chmiela{ 160eafa96e0SStanisław Chmiela UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; 161eafa96e0SStanisław Chmiela [self changePreviewOrientation:orientation]; 162eafa96e0SStanisław Chmiela} 163eafa96e0SStanisław Chmiela 164eafa96e0SStanisław Chmiela- (void)changePreviewOrientation:(UIInterfaceOrientation)orientation 165eafa96e0SStanisław Chmiela{ 166*314e850cSTomasz Sapeta EX_WEAKIFY(self); 167eafa96e0SStanisław Chmiela AVCaptureVideoOrientation videoOrientation = [EXBarCodeScannerUtils videoOrientationForInterfaceOrientation:orientation]; 168*314e850cSTomasz Sapeta [EXUtilities performSynchronouslyOnMainThread:^{ 169*314e850cSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 170eafa96e0SStanisław Chmiela if (self.previewLayer.connection.isVideoOrientationSupported) { 171eafa96e0SStanisław Chmiela [self.previewLayer.connection setVideoOrientation:videoOrientation]; 172eafa96e0SStanisław Chmiela } 173eafa96e0SStanisław Chmiela }]; 174eafa96e0SStanisław Chmiela} 175eafa96e0SStanisław Chmiela 176eafa96e0SStanisław Chmiela# pragma mark - session 177eafa96e0SStanisław Chmiela 178eafa96e0SStanisław Chmiela- (BOOL)ensurePermissionsGranted 179eafa96e0SStanisław Chmiela{ 180284d4840SŁukasz Kosmaty if (![_permissionsManager hasGrantedPermissionUsingRequesterClass:[EXBareCodeCameraRequester class]]) { 181eafa96e0SStanisław Chmiela [self onMountingError:@{@"message": @"Camera permissions not granted - component could not be rendered."}]; 182eafa96e0SStanisław Chmiela return FALSE; 183eafa96e0SStanisław Chmiela } 184eafa96e0SStanisław Chmiela return TRUE; 185eafa96e0SStanisław Chmiela} 186eafa96e0SStanisław Chmiela 187eafa96e0SStanisław Chmiela- (void)initializeSession 188eafa96e0SStanisław Chmiela{ 189eafa96e0SStanisław Chmiela if (_videoCaptureDeviceInput.device.position == _presetCamera) { 190eafa96e0SStanisław Chmiela return; 191eafa96e0SStanisław Chmiela } 192eafa96e0SStanisław Chmiela 193eafa96e0SStanisław Chmiela __block UIInterfaceOrientation interfaceOrientation; 194*314e850cSTomasz Sapeta [EXUtilities performSynchronouslyOnMainThread:^{ 195eafa96e0SStanisław Chmiela interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation]; 196eafa96e0SStanisław Chmiela }]; 197eafa96e0SStanisław Chmiela AVCaptureVideoOrientation orientation = [EXBarCodeScannerUtils videoOrientationForInterfaceOrientation:interfaceOrientation]; 198eafa96e0SStanisław Chmiela 199*314e850cSTomasz Sapeta EX_WEAKIFY(self); 200eafa96e0SStanisław Chmiela dispatch_async(_sessionQueue, ^{ 201*314e850cSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 202eafa96e0SStanisław Chmiela 203eafa96e0SStanisław Chmiela [self.session beginConfiguration]; 204eafa96e0SStanisław Chmiela 205eafa96e0SStanisław Chmiela NSError *error = nil; 206eafa96e0SStanisław Chmiela AVCaptureDevice *captureDevice = [EXBarCodeScannerUtils deviceWithMediaType:AVMediaTypeVideo 207eafa96e0SStanisław Chmiela preferringPosition:self.presetCamera]; 208eafa96e0SStanisław Chmiela AVCaptureDeviceInput *captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error]; 209eafa96e0SStanisław Chmiela 210eafa96e0SStanisław Chmiela if (error || captureDeviceInput == nil) { 211eafa96e0SStanisław Chmiela NSString *errorMessage = @"Camera could not be started - "; 212eafa96e0SStanisław Chmiela if (error) { 213eafa96e0SStanisław Chmiela errorMessage = [errorMessage stringByAppendingString:[error description]]; 214eafa96e0SStanisław Chmiela } else { 215eafa96e0SStanisław Chmiela errorMessage = [errorMessage stringByAppendingString:@"there's no captureDeviceInput available"]; 216eafa96e0SStanisław Chmiela } 217eafa96e0SStanisław Chmiela [self onMountingError:@{@"message": errorMessage}]; 218eafa96e0SStanisław Chmiela return; 219eafa96e0SStanisław Chmiela } 220eafa96e0SStanisław Chmiela 221eafa96e0SStanisław Chmiela [self.session removeInput:self.videoCaptureDeviceInput]; 222eafa96e0SStanisław Chmiela if ([self.session canAddInput:captureDeviceInput]) { 223eafa96e0SStanisław Chmiela [self.session addInput:captureDeviceInput]; 224eafa96e0SStanisław Chmiela 225eafa96e0SStanisław Chmiela self.videoCaptureDeviceInput = captureDeviceInput; 226eafa96e0SStanisław Chmiela [self.previewLayer.connection setVideoOrientation:orientation]; 227eafa96e0SStanisław Chmiela } 228eafa96e0SStanisław Chmiela 229eafa96e0SStanisław Chmiela [self.session commitConfiguration]; 230eafa96e0SStanisław Chmiela if (!self.session.isRunning) { 231eafa96e0SStanisław Chmiela [self startSession]; 232eafa96e0SStanisław Chmiela } 233eafa96e0SStanisław Chmiela }); 234eafa96e0SStanisław Chmiela} 235eafa96e0SStanisław Chmiela 236eafa96e0SStanisław Chmiela- (void)startSession 237eafa96e0SStanisław Chmiela{ 2386a392fcaSStanisław Chmiela#pragma clang diagnostic push 2396a392fcaSStanisław Chmiela#pragma clang diagnostic ignored "-Wunreachable-code" 240eafa96e0SStanisław Chmiela#if TARGET_IPHONE_SIMULATOR 241eafa96e0SStanisław Chmiela return; 242eafa96e0SStanisław Chmiela#endif 243eafa96e0SStanisław Chmiela if (![self ensurePermissionsGranted]) { 244eafa96e0SStanisław Chmiela return; 245eafa96e0SStanisław Chmiela }; 246eafa96e0SStanisław Chmiela 247*314e850cSTomasz Sapeta EX_WEAKIFY(self); 248eafa96e0SStanisław Chmiela dispatch_async(_sessionQueue, ^{ 249*314e850cSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 250eafa96e0SStanisław Chmiela 251eafa96e0SStanisław Chmiela if (self.presetCamera == AVCaptureDevicePositionUnspecified) { 252eafa96e0SStanisław Chmiela return; 253eafa96e0SStanisław Chmiela } 254eafa96e0SStanisław Chmiela 255eafa96e0SStanisław Chmiela [self setRuntimeErrorHandlingObserver: 256eafa96e0SStanisław Chmiela [[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureSessionRuntimeErrorNotification 257eafa96e0SStanisław Chmiela object:self.session 258eafa96e0SStanisław Chmiela queue:nil 259eafa96e0SStanisław Chmiela usingBlock:^(NSNotification *note) { 260*314e850cSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 261eafa96e0SStanisław Chmiela dispatch_async(self.sessionQueue, ^{ 262*314e850cSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 263eafa96e0SStanisław Chmiela // Manually restarting the session since it must have been stopped due to an error. 264eafa96e0SStanisław Chmiela [self.session startRunning]; 265eafa96e0SStanisław Chmiela [self onReady]; 266eafa96e0SStanisław Chmiela }); 267eafa96e0SStanisław Chmiela }]]; 268eafa96e0SStanisław Chmiela 269eafa96e0SStanisław Chmiela [self.barCodeScanner maybeStartBarCodeScanning]; 270eafa96e0SStanisław Chmiela 271eafa96e0SStanisław Chmiela [self.session startRunning]; 272eafa96e0SStanisław Chmiela [self onReady]; 273eafa96e0SStanisław Chmiela }); 2746a392fcaSStanisław Chmiela#pragma clang diagnostic pop 275eafa96e0SStanisław Chmiela} 276eafa96e0SStanisław Chmiela 277eafa96e0SStanisław Chmiela- (void)stopSession 278eafa96e0SStanisław Chmiela{ 279eafa96e0SStanisław Chmiela#if TARGET_IPHONE_SIMULATOR 280eafa96e0SStanisław Chmiela return; 281eafa96e0SStanisław Chmiela#endif 282*314e850cSTomasz Sapeta EX_WEAKIFY(self); 283eafa96e0SStanisław Chmiela dispatch_async(_sessionQueue, ^{ 284*314e850cSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 285eafa96e0SStanisław Chmiela 286eafa96e0SStanisław Chmiela [self.barCodeScanner stopBarCodeScanning]; 287eafa96e0SStanisław Chmiela 288eafa96e0SStanisław Chmiela [self.previewLayer removeFromSuperlayer]; 289eafa96e0SStanisław Chmiela [self.session commitConfiguration]; 290eafa96e0SStanisław Chmiela [self.session stopRunning]; 291eafa96e0SStanisław Chmiela for (AVCaptureInput *input in self.session.inputs) { 292eafa96e0SStanisław Chmiela [self.session removeInput:input]; 293eafa96e0SStanisław Chmiela } 294eafa96e0SStanisław Chmiela 295eafa96e0SStanisław Chmiela for (AVCaptureOutput *output in self.session.outputs) { 296eafa96e0SStanisław Chmiela [self.session removeOutput:output]; 297eafa96e0SStanisław Chmiela } 298eafa96e0SStanisław Chmiela }); 299eafa96e0SStanisław Chmiela} 300eafa96e0SStanisław Chmiela 301eafa96e0SStanisław Chmiela# pragma mark - BarCode scanner 302eafa96e0SStanisław Chmiela 303eafa96e0SStanisław Chmiela- (EXBarCodeScanner *)createBarCodeScanner 304eafa96e0SStanisław Chmiela{ 305eafa96e0SStanisław Chmiela EXBarCodeScanner *barCodeScanner = [EXBarCodeScanner new]; 306eafa96e0SStanisław Chmiela [barCodeScanner setSession:_session]; 307eafa96e0SStanisław Chmiela [barCodeScanner setSessionQueue:_sessionQueue]; 308*314e850cSTomasz Sapeta EX_WEAKIFY(self); 309eafa96e0SStanisław Chmiela [barCodeScanner setOnBarCodeScanned:^(NSDictionary *body) { 310*314e850cSTomasz Sapeta EX_ENSURE_STRONGIFY(self); 311eafa96e0SStanisław Chmiela [self onBarCodeScanned:body]; 312eafa96e0SStanisław Chmiela }]; 313eafa96e0SStanisław Chmiela [barCodeScanner setIsEnabled:true]; 314eafa96e0SStanisław Chmiela return barCodeScanner; 315eafa96e0SStanisław Chmiela} 316eafa96e0SStanisław Chmiela 317eafa96e0SStanisław Chmiela@end 318