cronapp-cordova-plugin-barcodescanner 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1072 @@
1
+ /*
2
+ * PhoneGap is available under *either* the terms of the modified BSD license *or* the
3
+ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
4
+ *
5
+ * Copyright 2011 Matt Kane. All rights reserved.
6
+ * Copyright (c) 2011, IBM Corporation
7
+ */
8
+
9
+ #import <AVFoundation/AVFoundation.h>
10
+ #import <AssetsLibrary/AssetsLibrary.h>
11
+ #import <Cordova/CDVPlugin.h>
12
+
13
+
14
+ //------------------------------------------------------------------------------
15
+ // Delegate to handle orientation functions
16
+ //------------------------------------------------------------------------------
17
+ @protocol CDVBarcodeScannerOrientationDelegate <NSObject>
18
+
19
+ - (NSUInteger)supportedInterfaceOrientations;
20
+ - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
21
+ - (BOOL)shouldAutorotate;
22
+
23
+ @end
24
+
25
+ //------------------------------------------------------------------------------
26
+ // Adds a shutter button to the UI, and changes the scan from continuous to
27
+ // only performing a scan when you click the shutter button. For testing.
28
+ //------------------------------------------------------------------------------
29
+ #define USE_SHUTTER 0
30
+
31
+ //------------------------------------------------------------------------------
32
+ @class CDVbcsProcessor;
33
+ @class CDVbcsViewController;
34
+
35
+ //------------------------------------------------------------------------------
36
+ // plugin class
37
+ //------------------------------------------------------------------------------
38
+ @interface CDVBarcodeScanner : CDVPlugin {}
39
+ - (NSString*)isScanNotPossible;
40
+ - (void)scan:(CDVInvokedUrlCommand*)command;
41
+ - (void)encode:(CDVInvokedUrlCommand*)command;
42
+ - (void)returnImage:(NSString*)filePath format:(NSString*)format callback:(NSString*)callback;
43
+ - (void)returnSuccess:(NSString*)scannedText format:(NSString*)format cancelled:(BOOL)cancelled flipped:(BOOL)flipped callback:(NSString*)callback;
44
+ - (void)returnError:(NSString*)message callback:(NSString*)callback;
45
+ @end
46
+
47
+ //------------------------------------------------------------------------------
48
+ // class that does the grunt work
49
+ //------------------------------------------------------------------------------
50
+ @interface CDVbcsProcessor : NSObject <AVCaptureMetadataOutputObjectsDelegate> {}
51
+ @property (nonatomic, retain) CDVBarcodeScanner* plugin;
52
+ @property (nonatomic, retain) NSString* callback;
53
+ @property (nonatomic, retain) UIViewController* parentViewController;
54
+ @property (nonatomic, retain) CDVbcsViewController* viewController;
55
+ @property (nonatomic, retain) AVCaptureSession* captureSession;
56
+ @property (nonatomic, retain) AVCaptureVideoPreviewLayer* previewLayer;
57
+ @property (nonatomic, retain) NSString* alternateXib;
58
+ @property (nonatomic, retain) NSMutableArray* results;
59
+ @property (nonatomic, retain) NSString* formats;
60
+ @property (nonatomic) BOOL is1D;
61
+ @property (nonatomic) BOOL is2D;
62
+ @property (nonatomic) BOOL capturing;
63
+ @property (nonatomic) BOOL isFrontCamera;
64
+ @property (nonatomic) BOOL isShowFlipCameraButton;
65
+ @property (nonatomic) BOOL isShowTorchButton;
66
+ @property (nonatomic) BOOL isFlipped;
67
+ @property (nonatomic) BOOL isTransitionAnimated;
68
+ @property (nonatomic) BOOL isSuccessBeepEnabled;
69
+
70
+
71
+ - (id)initWithPlugin:(CDVBarcodeScanner*)plugin callback:(NSString*)callback parentViewController:(UIViewController*)parentViewController alterateOverlayXib:(NSString *)alternateXib;
72
+ - (void)scanBarcode;
73
+ - (void)barcodeScanSucceeded:(NSString*)text format:(NSString*)format;
74
+ - (void)barcodeScanFailed:(NSString*)message;
75
+ - (void)barcodeScanCancelled;
76
+ - (void)openDialog;
77
+ - (NSString*)setUpCaptureSession;
78
+ - (void)captureOutput:(AVCaptureOutput*)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection*)connection;
79
+ @end
80
+
81
+ //------------------------------------------------------------------------------
82
+ // Qr encoder processor
83
+ //------------------------------------------------------------------------------
84
+ @interface CDVqrProcessor: NSObject
85
+ @property (nonatomic, retain) CDVBarcodeScanner* plugin;
86
+ @property (nonatomic, retain) NSString* callback;
87
+ @property (nonatomic, retain) NSString* stringToEncode;
88
+ @property NSInteger size;
89
+
90
+ - (id)initWithPlugin:(CDVBarcodeScanner*)plugin callback:(NSString*)callback stringToEncode:(NSString*)stringToEncode;
91
+ - (void)generateImage;
92
+ @end
93
+
94
+ //------------------------------------------------------------------------------
95
+ // view controller for the ui
96
+ //------------------------------------------------------------------------------
97
+ @interface CDVbcsViewController : UIViewController <CDVBarcodeScannerOrientationDelegate> {}
98
+ @property (nonatomic, retain) CDVbcsProcessor* processor;
99
+ @property (nonatomic, retain) NSString* alternateXib;
100
+ @property (nonatomic) BOOL shutterPressed;
101
+ @property (nonatomic, retain) IBOutlet UIView* overlayView;
102
+ @property (nonatomic, retain) UIToolbar * toolbar;
103
+ @property (nonatomic, retain) UIView * reticleView;
104
+ // unsafe_unretained is equivalent to assign - used to prevent retain cycles in the property below
105
+ @property (nonatomic, unsafe_unretained) id orientationDelegate;
106
+
107
+ - (id)initWithProcessor:(CDVbcsProcessor*)processor alternateOverlay:(NSString *)alternateXib;
108
+ - (void)startCapturing;
109
+ - (UIView*)buildOverlayView;
110
+ - (UIImage*)buildReticleImage;
111
+ - (void)shutterButtonPressed;
112
+ - (IBAction)cancelButtonPressed:(id)sender;
113
+ - (IBAction)flipCameraButtonPressed:(id)sender;
114
+ - (IBAction)torchButtonPressed:(id)sender;
115
+
116
+ @end
117
+
118
+ //------------------------------------------------------------------------------
119
+ // plugin class
120
+ //------------------------------------------------------------------------------
121
+ @implementation CDVBarcodeScanner
122
+
123
+ //--------------------------------------------------------------------------
124
+ - (NSString*)isScanNotPossible {
125
+ NSString* result = nil;
126
+
127
+ Class aClass = NSClassFromString(@"AVCaptureSession");
128
+ if (aClass == nil) {
129
+ return @"AVFoundation Framework not available";
130
+ }
131
+
132
+ return result;
133
+ }
134
+
135
+ -(BOOL)notHasPermission
136
+ {
137
+ AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
138
+ return (authStatus == AVAuthorizationStatusDenied ||
139
+ authStatus == AVAuthorizationStatusRestricted);
140
+ }
141
+
142
+ -(BOOL)isUsageDescriptionSet
143
+ {
144
+ NSDictionary * plist = [[NSBundle mainBundle] infoDictionary];
145
+ if ([plist objectForKey:@"NSCameraUsageDescription" ] ||
146
+ [[NSBundle mainBundle] localizedStringForKey: @"NSCameraUsageDescription" value: nil table: @"InfoPlist"]) {
147
+ return YES;
148
+ }
149
+ return NO;
150
+ }
151
+
152
+
153
+
154
+ //--------------------------------------------------------------------------
155
+ - (void)scan:(CDVInvokedUrlCommand*)command {
156
+ CDVbcsProcessor* processor;
157
+ NSString* callback;
158
+ NSString* capabilityError;
159
+
160
+ callback = command.callbackId;
161
+
162
+ NSDictionary* options;
163
+ if (command.arguments.count == 0) {
164
+ options = [NSDictionary dictionary];
165
+ } else {
166
+ options = command.arguments[0];
167
+ }
168
+
169
+ BOOL preferFrontCamera = [options[@"preferFrontCamera"] boolValue];
170
+ BOOL showFlipCameraButton = [options[@"showFlipCameraButton"] boolValue];
171
+ BOOL showTorchButton = [options[@"showTorchButton"] boolValue];
172
+ BOOL disableAnimations = [options[@"disableAnimations"] boolValue];
173
+ BOOL disableSuccessBeep = [options[@"disableSuccessBeep"] boolValue];
174
+
175
+ // We allow the user to define an alternate xib file for loading the overlay.
176
+ NSString *overlayXib = options[@"overlayXib"];
177
+
178
+ capabilityError = [self isScanNotPossible];
179
+ if (capabilityError) {
180
+ [self returnError:capabilityError callback:callback];
181
+ return;
182
+ } else if ([self notHasPermission]) {
183
+ NSString * error = NSLocalizedString(@"Access to the camera has been prohibited; please enable it in the Settings app to continue.",nil);
184
+ [self returnError:error callback:callback];
185
+ return;
186
+ } else if (![self isUsageDescriptionSet]) {
187
+ NSString * error = NSLocalizedString(@"NSCameraUsageDescription is not set in the info.plist", nil);
188
+ [self returnError:error callback:callback];
189
+ return;
190
+ }
191
+
192
+ processor = [[CDVbcsProcessor alloc]
193
+ initWithPlugin:self
194
+ callback:callback
195
+ parentViewController:self.viewController
196
+ alterateOverlayXib:overlayXib
197
+ ];
198
+ // queue [processor scanBarcode] to run on the event loop
199
+
200
+ if (preferFrontCamera) {
201
+ processor.isFrontCamera = true;
202
+ }
203
+
204
+ if (showFlipCameraButton) {
205
+ processor.isShowFlipCameraButton = true;
206
+ }
207
+
208
+ if (showTorchButton) {
209
+ processor.isShowTorchButton = true;
210
+ }
211
+
212
+ processor.isSuccessBeepEnabled = !disableSuccessBeep;
213
+
214
+ processor.isTransitionAnimated = !disableAnimations;
215
+
216
+ processor.formats = options[@"formats"];
217
+
218
+ [processor performSelector:@selector(scanBarcode) withObject:nil afterDelay:0];
219
+ }
220
+
221
+ //--------------------------------------------------------------------------
222
+ - (void)encode:(CDVInvokedUrlCommand*)command {
223
+ if([command.arguments count] < 1)
224
+ [self returnError:@"Too few arguments!" callback:command.callbackId];
225
+
226
+ CDVqrProcessor* processor;
227
+ NSString* callback;
228
+ callback = command.callbackId;
229
+
230
+ processor = [[CDVqrProcessor alloc]
231
+ initWithPlugin:self
232
+ callback:callback
233
+ stringToEncode: command.arguments[0][@"data"]
234
+ ];
235
+ // queue [processor generateImage] to run on the event loop
236
+ [processor performSelector:@selector(generateImage) withObject:nil afterDelay:0];
237
+ }
238
+
239
+ - (void)returnImage:(NSString*)filePath format:(NSString*)format callback:(NSString*)callback{
240
+ NSMutableDictionary* resultDict = [[NSMutableDictionary alloc] init];
241
+ resultDict[@"format"] = format;
242
+ resultDict[@"file"] = filePath;
243
+
244
+ CDVPluginResult* result = [CDVPluginResult
245
+ resultWithStatus: CDVCommandStatus_OK
246
+ messageAsDictionary:resultDict
247
+ ];
248
+
249
+ [[self commandDelegate] sendPluginResult:result callbackId:callback];
250
+ }
251
+
252
+ //--------------------------------------------------------------------------
253
+ - (void)returnSuccess:(NSString*)scannedText format:(NSString*)format cancelled:(BOOL)cancelled flipped:(BOOL)flipped callback:(NSString*)callback{
254
+ NSNumber* cancelledNumber = @(cancelled ? 1 : 0);
255
+
256
+ NSMutableDictionary* resultDict = [NSMutableDictionary new];
257
+ resultDict[@"text"] = scannedText;
258
+ resultDict[@"format"] = format;
259
+ resultDict[@"cancelled"] = cancelledNumber;
260
+
261
+ CDVPluginResult* result = [CDVPluginResult
262
+ resultWithStatus: CDVCommandStatus_OK
263
+ messageAsDictionary: resultDict
264
+ ];
265
+ [self.commandDelegate sendPluginResult:result callbackId:callback];
266
+ }
267
+
268
+ //--------------------------------------------------------------------------
269
+ - (void)returnError:(NSString*)message callback:(NSString*)callback {
270
+ CDVPluginResult* result = [CDVPluginResult
271
+ resultWithStatus: CDVCommandStatus_ERROR
272
+ messageAsString: message
273
+ ];
274
+
275
+ [self.commandDelegate sendPluginResult:result callbackId:callback];
276
+ }
277
+
278
+ @end
279
+
280
+ //------------------------------------------------------------------------------
281
+ // class that does the grunt work
282
+ //------------------------------------------------------------------------------
283
+ @implementation CDVbcsProcessor
284
+
285
+ @synthesize plugin = _plugin;
286
+ @synthesize callback = _callback;
287
+ @synthesize parentViewController = _parentViewController;
288
+ @synthesize viewController = _viewController;
289
+ @synthesize captureSession = _captureSession;
290
+ @synthesize previewLayer = _previewLayer;
291
+ @synthesize alternateXib = _alternateXib;
292
+ @synthesize is1D = _is1D;
293
+ @synthesize is2D = _is2D;
294
+ @synthesize capturing = _capturing;
295
+ @synthesize results = _results;
296
+
297
+ SystemSoundID _soundFileObject;
298
+
299
+ //--------------------------------------------------------------------------
300
+ - (id)initWithPlugin:(CDVBarcodeScanner*)plugin
301
+ callback:(NSString*)callback
302
+ parentViewController:(UIViewController*)parentViewController
303
+ alterateOverlayXib:(NSString *)alternateXib {
304
+ self = [super init];
305
+ if (!self) return self;
306
+
307
+ self.plugin = plugin;
308
+ self.callback = callback;
309
+ self.parentViewController = parentViewController;
310
+ self.alternateXib = alternateXib;
311
+
312
+ self.is1D = YES;
313
+ self.is2D = YES;
314
+ self.capturing = NO;
315
+ self.results = [NSMutableArray new];
316
+
317
+ CFURLRef soundFileURLRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("CDVBarcodeScanner.bundle/beep"), CFSTR ("caf"), NULL);
318
+ AudioServicesCreateSystemSoundID(soundFileURLRef, &_soundFileObject);
319
+
320
+ return self;
321
+ }
322
+
323
+ //--------------------------------------------------------------------------
324
+ - (void)dealloc {
325
+ self.plugin = nil;
326
+ self.callback = nil;
327
+ self.parentViewController = nil;
328
+ self.viewController = nil;
329
+ self.captureSession = nil;
330
+ self.previewLayer = nil;
331
+ self.alternateXib = nil;
332
+ self.results = nil;
333
+
334
+ self.capturing = NO;
335
+
336
+ AudioServicesRemoveSystemSoundCompletion(_soundFileObject);
337
+ AudioServicesDisposeSystemSoundID(_soundFileObject);
338
+ }
339
+
340
+ //--------------------------------------------------------------------------
341
+ - (void)scanBarcode {
342
+
343
+ // self.captureSession = nil;
344
+ // self.previewLayer = nil;
345
+ NSString* errorMessage = [self setUpCaptureSession];
346
+ if (errorMessage) {
347
+ [self barcodeScanFailed:errorMessage];
348
+ return;
349
+ }
350
+
351
+ self.viewController = [[CDVbcsViewController alloc] initWithProcessor: self alternateOverlay:self.alternateXib];
352
+ // here we set the orientation delegate to the MainViewController of the app (orientation controlled in the Project Settings)
353
+ self.viewController.orientationDelegate = self.plugin.viewController;
354
+
355
+ // delayed [self openDialog];
356
+ [self performSelector:@selector(openDialog) withObject:nil afterDelay:1];
357
+ }
358
+
359
+ //--------------------------------------------------------------------------
360
+ - (void)openDialog {
361
+ [self.parentViewController
362
+ presentViewController:self.viewController
363
+ animated:self.isTransitionAnimated completion:nil
364
+ ];
365
+ }
366
+
367
+ //--------------------------------------------------------------------------
368
+ - (void)barcodeScanDone:(void (^)(void))callbackBlock {
369
+ self.capturing = NO;
370
+ [self.captureSession stopRunning];
371
+ [self.parentViewController dismissViewControllerAnimated:self.isTransitionAnimated completion:callbackBlock];
372
+
373
+
374
+ AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
375
+ [device lockForConfiguration:nil];
376
+ if([device isAutoFocusRangeRestrictionSupported]) {
377
+ [device setAutoFocusRangeRestriction:AVCaptureAutoFocusRangeRestrictionNone];
378
+ }
379
+ [device unlockForConfiguration];
380
+
381
+ // viewcontroller holding onto a reference to us, release them so they
382
+ // will release us
383
+ self.viewController = nil;
384
+ }
385
+
386
+ //--------------------------------------------------------------------------
387
+ - (BOOL)checkResult:(NSString *)result {
388
+ [self.results addObject:result];
389
+
390
+ NSInteger treshold = 7;
391
+
392
+ if (self.results.count > treshold) {
393
+ [self.results removeObjectAtIndex:0];
394
+ }
395
+
396
+ if (self.results.count < treshold)
397
+ {
398
+ return NO;
399
+ }
400
+
401
+ BOOL allEqual = YES;
402
+ NSString *compareString = self.results[0];
403
+
404
+ for (NSString *aResult in self.results)
405
+ {
406
+ if (![compareString isEqualToString:aResult])
407
+ {
408
+ allEqual = NO;
409
+ //NSLog(@"Did not fit: %@",self.results);
410
+ break;
411
+ }
412
+ }
413
+
414
+ return allEqual;
415
+ }
416
+
417
+ //--------------------------------------------------------------------------
418
+ - (void)barcodeScanSucceeded:(NSString*)text format:(NSString*)format {
419
+ dispatch_sync(dispatch_get_main_queue(), ^{
420
+ if (self.isSuccessBeepEnabled) {
421
+ AudioServicesPlaySystemSound(_soundFileObject);
422
+ }
423
+ [self barcodeScanDone:^{
424
+ [self.plugin returnSuccess:text format:format cancelled:FALSE flipped:FALSE callback:self.callback];
425
+ }];
426
+ });
427
+ }
428
+
429
+ //--------------------------------------------------------------------------
430
+ - (void)barcodeScanFailed:(NSString*)message {
431
+ dispatch_block_t block = ^{
432
+ [self barcodeScanDone:^{
433
+ [self.plugin returnError:message callback:self.callback];
434
+ }];
435
+ };
436
+ if ([NSThread isMainThread]) {
437
+ block();
438
+ } else {
439
+ dispatch_sync(dispatch_get_main_queue(), block);
440
+ }
441
+ }
442
+
443
+ //--------------------------------------------------------------------------
444
+ - (void)barcodeScanCancelled {
445
+ [self barcodeScanDone:^{
446
+ [self.plugin returnSuccess:@"" format:@"" cancelled:TRUE flipped:self.isFlipped callback:self.callback];
447
+ }];
448
+ if (self.isFlipped) {
449
+ self.isFlipped = NO;
450
+ }
451
+ }
452
+
453
+ - (void)flipCamera {
454
+ self.isFlipped = YES;
455
+ self.isFrontCamera = !self.isFrontCamera;
456
+ [self barcodeScanDone:^{
457
+ if (self.isFlipped) {
458
+ self.isFlipped = NO;
459
+ }
460
+ [self performSelector:@selector(scanBarcode) withObject:nil afterDelay:0.1];
461
+ }];
462
+ }
463
+
464
+ - (void)toggleTorch {
465
+ AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
466
+ [device lockForConfiguration:nil];
467
+ if (device.flashActive) {
468
+ [device setTorchMode:AVCaptureTorchModeOff];
469
+ [device setFlashMode:AVCaptureFlashModeOff];
470
+ } else {
471
+ [device setTorchModeOnWithLevel:AVCaptureMaxAvailableTorchLevel error:nil];
472
+ [device setFlashMode:AVCaptureFlashModeOn];
473
+ }
474
+ [device unlockForConfiguration];
475
+ }
476
+
477
+ //--------------------------------------------------------------------------
478
+ - (NSString*)setUpCaptureSession {
479
+ NSError* error = nil;
480
+
481
+ AVCaptureSession* captureSession = [[AVCaptureSession alloc] init];
482
+ self.captureSession = captureSession;
483
+
484
+ AVCaptureDevice* __block device = nil;
485
+ if (self.isFrontCamera) {
486
+
487
+ NSArray* devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
488
+ [devices enumerateObjectsUsingBlock:^(AVCaptureDevice *obj, NSUInteger idx, BOOL *stop) {
489
+ if (obj.position == AVCaptureDevicePositionFront) {
490
+ device = obj;
491
+ }
492
+ }];
493
+ } else {
494
+ device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
495
+ if (!device) return @"unable to obtain video capture device";
496
+
497
+ }
498
+
499
+ // set focus params if available to improve focusing
500
+ [device lockForConfiguration:&error];
501
+ if (error == nil) {
502
+ if([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
503
+ [device setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
504
+ }
505
+ if([device isAutoFocusRangeRestrictionSupported]) {
506
+ [device setAutoFocusRangeRestriction:AVCaptureAutoFocusRangeRestrictionNear];
507
+ }
508
+ }
509
+ [device unlockForConfiguration];
510
+
511
+ AVCaptureDeviceInput* input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
512
+ if (!input) return @"unable to obtain video capture device input";
513
+
514
+ AVCaptureMetadataOutput* output = [[AVCaptureMetadataOutput alloc] init];
515
+ if (!output) return @"unable to obtain video capture output";
516
+
517
+ [output setMetadataObjectsDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)];
518
+
519
+ if ([captureSession canSetSessionPreset:AVCaptureSessionPresetHigh]) {
520
+ captureSession.sessionPreset = AVCaptureSessionPresetHigh;
521
+ } else if ([captureSession canSetSessionPreset:AVCaptureSessionPresetMedium]) {
522
+ captureSession.sessionPreset = AVCaptureSessionPresetMedium;
523
+ } else {
524
+ return @"unable to preset high nor medium quality video capture";
525
+ }
526
+
527
+ if ([captureSession canAddInput:input]) {
528
+ [captureSession addInput:input];
529
+ }
530
+ else {
531
+ return @"unable to add video capture device input to session";
532
+ }
533
+
534
+ if ([captureSession canAddOutput:output]) {
535
+ [captureSession addOutput:output];
536
+ }
537
+ else {
538
+ return @"unable to add video capture output to session";
539
+ }
540
+
541
+ [output setMetadataObjectTypes:[self formatObjectTypes]];
542
+
543
+ // setup capture preview layer
544
+ self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
545
+ self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
546
+
547
+ // run on next event loop pass [captureSession startRunning]
548
+ [captureSession performSelector:@selector(startRunning) withObject:nil afterDelay:0];
549
+
550
+ return nil;
551
+ }
552
+
553
+ //--------------------------------------------------------------------------
554
+ // this method gets sent the captured frames
555
+ //--------------------------------------------------------------------------
556
+ - (void)captureOutput:(AVCaptureOutput*)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection*)connection {
557
+
558
+ if (!self.capturing) return;
559
+
560
+ #if USE_SHUTTER
561
+ if (!self.viewController.shutterPressed) return;
562
+ self.viewController.shutterPressed = NO;
563
+
564
+ UIView* flashView = [[UIView alloc] initWithFrame:self.viewController.view.frame];
565
+ [flashView setBackgroundColor:[UIColor whiteColor]];
566
+ [self.viewController.view.window addSubview:flashView];
567
+
568
+ [UIView
569
+ animateWithDuration:.4f
570
+ animations:^{
571
+ [flashView setAlpha:0.f];
572
+ }
573
+ completion:^(BOOL finished){
574
+ [flashView removeFromSuperview];
575
+ }
576
+ ];
577
+ #endif
578
+
579
+
580
+ try {
581
+ // This will bring in multiple entities if there are multiple 2D codes in frame.
582
+ for (AVMetadataObject *metaData in metadataObjects) {
583
+ AVMetadataMachineReadableCodeObject* code = (AVMetadataMachineReadableCodeObject*)[self.previewLayer transformedMetadataObjectForMetadataObject:(AVMetadataMachineReadableCodeObject*)metaData];
584
+
585
+ if ([self checkResult:code.stringValue]) {
586
+ [self barcodeScanSucceeded:code.stringValue format:[self formatStringFromMetadata:code]];
587
+ }
588
+ }
589
+ }
590
+ catch (...) {
591
+ // NSLog(@"decoding: unknown exception");
592
+ // [self barcodeScanFailed:@"unknown exception decoding barcode"];
593
+ }
594
+
595
+ // NSTimeInterval timeElapsed = [NSDate timeIntervalSinceReferenceDate] - timeStart;
596
+ // NSLog(@"decoding completed in %dms", (int) (timeElapsed * 1000));
597
+
598
+ }
599
+
600
+ //--------------------------------------------------------------------------
601
+ // convert metadata object information to barcode format string
602
+ //--------------------------------------------------------------------------
603
+ - (NSString*)formatStringFromMetadata:(AVMetadataMachineReadableCodeObject*)format {
604
+ if (format.type == AVMetadataObjectTypeQRCode) return @"QR_CODE";
605
+ if (format.type == AVMetadataObjectTypeAztecCode) return @"AZTEC";
606
+ if (format.type == AVMetadataObjectTypeDataMatrixCode) return @"DATA_MATRIX";
607
+ if (format.type == AVMetadataObjectTypeUPCECode) return @"UPC_E";
608
+ // According to Apple documentation, UPC_A is EAN13 with a leading 0.
609
+ if (format.type == AVMetadataObjectTypeEAN13Code && [format.stringValue characterAtIndex:0] == '0') return @"UPC_A";
610
+ if (format.type == AVMetadataObjectTypeEAN8Code) return @"EAN_8";
611
+ if (format.type == AVMetadataObjectTypeEAN13Code) return @"EAN_13";
612
+ if (format.type == AVMetadataObjectTypeCode128Code) return @"CODE_128";
613
+ if (format.type == AVMetadataObjectTypeCode93Code) return @"CODE_93";
614
+ if (format.type == AVMetadataObjectTypeCode39Code) return @"CODE_39";
615
+ if (format.type == AVMetadataObjectTypeInterleaved2of5Code) return @"ITF";
616
+ if (format.type == AVMetadataObjectTypeITF14Code) return @"ITF_14";
617
+
618
+ if (format.type == AVMetadataObjectTypePDF417Code) return @"PDF_417";
619
+ return @"???";
620
+ }
621
+
622
+ //--------------------------------------------------------------------------
623
+ // convert string formats to metadata objects
624
+ //--------------------------------------------------------------------------
625
+ - (NSArray*) formatObjectTypes {
626
+ NSArray *supportedFormats = nil;
627
+ if (self.formats != nil) {
628
+ supportedFormats = [self.formats componentsSeparatedByString:@","];
629
+ }
630
+
631
+ NSMutableArray * formatObjectTypes = [NSMutableArray array];
632
+
633
+ if (self.formats == nil || [supportedFormats containsObject:@"QR_CODE"]) [formatObjectTypes addObject:AVMetadataObjectTypeQRCode];
634
+ if (self.formats == nil || [supportedFormats containsObject:@"AZTEC"]) [formatObjectTypes addObject:AVMetadataObjectTypeAztecCode];
635
+ if (self.formats == nil || [supportedFormats containsObject:@"DATA_MATRIX"]) [formatObjectTypes addObject:AVMetadataObjectTypeDataMatrixCode];
636
+ if (self.formats == nil || [supportedFormats containsObject:@"UPC_E"]) [formatObjectTypes addObject:AVMetadataObjectTypeUPCECode];
637
+ if (self.formats == nil || [supportedFormats containsObject:@"EAN_8"]) [formatObjectTypes addObject:AVMetadataObjectTypeEAN8Code];
638
+ if (self.formats == nil || [supportedFormats containsObject:@"EAN_13"]) [formatObjectTypes addObject:AVMetadataObjectTypeEAN13Code];
639
+ if (self.formats == nil || [supportedFormats containsObject:@"CODE_128"]) [formatObjectTypes addObject:AVMetadataObjectTypeCode128Code];
640
+ if (self.formats == nil || [supportedFormats containsObject:@"CODE_93"]) [formatObjectTypes addObject:AVMetadataObjectTypeCode93Code];
641
+ if (self.formats == nil || [supportedFormats containsObject:@"CODE_39"]) [formatObjectTypes addObject:AVMetadataObjectTypeCode39Code];
642
+ if (self.formats == nil || [supportedFormats containsObject:@"ITF"]) [formatObjectTypes addObject:AVMetadataObjectTypeInterleaved2of5Code];
643
+ if (self.formats == nil || [supportedFormats containsObject:@"ITF_14"]) [formatObjectTypes addObject:AVMetadataObjectTypeITF14Code];
644
+ if (self.formats == nil || [supportedFormats containsObject:@"PDF_417"]) [formatObjectTypes addObject:AVMetadataObjectTypePDF417Code];
645
+
646
+ return formatObjectTypes;
647
+ }
648
+
649
+ @end
650
+
651
+ //------------------------------------------------------------------------------
652
+ // qr encoder processor
653
+ //------------------------------------------------------------------------------
654
+ @implementation CDVqrProcessor
655
+ @synthesize plugin = _plugin;
656
+ @synthesize callback = _callback;
657
+ @synthesize stringToEncode = _stringToEncode;
658
+ @synthesize size = _size;
659
+
660
+ - (id)initWithPlugin:(CDVBarcodeScanner*)plugin callback:(NSString*)callback stringToEncode:(NSString*)stringToEncode{
661
+ self = [super init];
662
+ if (!self) return self;
663
+
664
+ self.plugin = plugin;
665
+ self.callback = callback;
666
+ self.stringToEncode = stringToEncode;
667
+ self.size = 300;
668
+
669
+ return self;
670
+ }
671
+
672
+ //--------------------------------------------------------------------------
673
+ - (void)dealloc {
674
+ self.plugin = nil;
675
+ self.callback = nil;
676
+ self.stringToEncode = nil;
677
+ }
678
+ //--------------------------------------------------------------------------
679
+ - (void)generateImage{
680
+ /* setup qr filter */
681
+ CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
682
+ [filter setDefaults];
683
+
684
+ /* set filter's input message
685
+ * the encoding string has to be convert to a UTF-8 encoded NSData object */
686
+ [filter setValue:[self.stringToEncode dataUsingEncoding:NSUTF8StringEncoding]
687
+ forKey:@"inputMessage"];
688
+
689
+ /* on ios >= 7.0 set low image error correction level */
690
+ if (floor(NSFoundationVersionNumber) >= NSFoundationVersionNumber_iOS_7_0)
691
+ [filter setValue:@"L" forKey:@"inputCorrectionLevel"];
692
+
693
+ /* prepare cgImage */
694
+ CIImage *outputImage = [filter outputImage];
695
+ CIContext *context = [CIContext contextWithOptions:nil];
696
+ CGImageRef cgImage = [context createCGImage:outputImage
697
+ fromRect:[outputImage extent]];
698
+
699
+ /* returned qr code image */
700
+ UIImage *qrImage = [UIImage imageWithCGImage:cgImage
701
+ scale:1.
702
+ orientation:UIImageOrientationUp];
703
+ /* resize generated image */
704
+ CGFloat width = _size;
705
+ CGFloat height = _size;
706
+
707
+ UIGraphicsBeginImageContext(CGSizeMake(width, height));
708
+
709
+ CGContextRef ctx = UIGraphicsGetCurrentContext();
710
+ CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);
711
+ [qrImage drawInRect:CGRectMake(0, 0, width, height)];
712
+ qrImage = UIGraphicsGetImageFromCurrentImageContext();
713
+
714
+ /* clean up */
715
+ UIGraphicsEndImageContext();
716
+ CGImageRelease(cgImage);
717
+
718
+ /* save image to file */
719
+ NSString* fileName = [[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingString:@".jpg"];
720
+ NSString* filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
721
+ [UIImageJPEGRepresentation(qrImage, 1.0) writeToFile:filePath atomically:YES];
722
+
723
+ /* return file path back to cordova */
724
+ [self.plugin returnImage:filePath format:@"QR_CODE" callback: self.callback];
725
+ }
726
+ @end
727
+
728
+ //------------------------------------------------------------------------------
729
+ // view controller for the ui
730
+ //------------------------------------------------------------------------------
731
+ @implementation CDVbcsViewController
732
+ @synthesize processor = _processor;
733
+ @synthesize shutterPressed = _shutterPressed;
734
+ @synthesize alternateXib = _alternateXib;
735
+ @synthesize overlayView = _overlayView;
736
+
737
+ //--------------------------------------------------------------------------
738
+ - (id)initWithProcessor:(CDVbcsProcessor*)processor alternateOverlay:(NSString *)alternateXib {
739
+ self = [super init];
740
+ if (!self) return self;
741
+
742
+ self.processor = processor;
743
+ self.shutterPressed = NO;
744
+ self.alternateXib = alternateXib;
745
+ self.overlayView = nil;
746
+ return self;
747
+ }
748
+
749
+ //--------------------------------------------------------------------------
750
+ - (void)dealloc {
751
+ self.view = nil;
752
+ self.processor = nil;
753
+ self.shutterPressed = NO;
754
+ self.alternateXib = nil;
755
+ self.overlayView = nil;
756
+ }
757
+
758
+ //--------------------------------------------------------------------------
759
+ - (void)loadView {
760
+ self.view = [[UIView alloc] initWithFrame: self.processor.parentViewController.view.frame];
761
+ }
762
+
763
+ //--------------------------------------------------------------------------
764
+ - (void)viewWillAppear:(BOOL)animated {
765
+
766
+ // set video orientation to what the camera sees
767
+ self.processor.previewLayer.connection.videoOrientation = [self interfaceOrientationToVideoOrientation:[UIApplication sharedApplication].statusBarOrientation];
768
+
769
+ // this fixes the bug when the statusbar is landscape, and the preview layer
770
+ // starts up in portrait (not filling the whole view)
771
+ self.processor.previewLayer.frame = self.view.bounds;
772
+ }
773
+
774
+ //--------------------------------------------------------------------------
775
+ - (void)viewDidAppear:(BOOL)animated {
776
+ // setup capture preview layer
777
+ AVCaptureVideoPreviewLayer* previewLayer = self.processor.previewLayer;
778
+ previewLayer.frame = self.view.bounds;
779
+ previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
780
+
781
+ if ([previewLayer.connection isVideoOrientationSupported]) {
782
+ previewLayer.connection.videoOrientation = [self interfaceOrientationToVideoOrientation:[UIApplication sharedApplication].statusBarOrientation];
783
+ }
784
+
785
+ [self.view.layer insertSublayer:previewLayer below:[[self.view.layer sublayers] objectAtIndex:0]];
786
+
787
+ [self.view addSubview:[self buildOverlayView]];
788
+ [self startCapturing];
789
+
790
+ [super viewDidAppear:animated];
791
+ }
792
+
793
+ - (AVCaptureVideoOrientation)interfaceOrientationToVideoOrientation:(UIInterfaceOrientation)orientation {
794
+ switch (orientation) {
795
+ case UIInterfaceOrientationPortrait:
796
+ return AVCaptureVideoOrientationPortrait;
797
+ case UIInterfaceOrientationPortraitUpsideDown:
798
+ return AVCaptureVideoOrientationPortraitUpsideDown;
799
+ case UIInterfaceOrientationLandscapeLeft:
800
+ return AVCaptureVideoOrientationLandscapeLeft;
801
+ case UIInterfaceOrientationLandscapeRight:
802
+ return AVCaptureVideoOrientationLandscapeRight;
803
+ default:
804
+ return AVCaptureVideoOrientationPortrait;
805
+ }
806
+ }
807
+
808
+ //--------------------------------------------------------------------------
809
+ - (void)startCapturing {
810
+ self.processor.capturing = YES;
811
+ }
812
+
813
+ //--------------------------------------------------------------------------
814
+ - (IBAction)shutterButtonPressed {
815
+ self.shutterPressed = YES;
816
+ }
817
+
818
+ //--------------------------------------------------------------------------
819
+ - (IBAction)cancelButtonPressed:(id)sender {
820
+ [self.processor performSelector:@selector(barcodeScanCancelled) withObject:nil afterDelay:0];
821
+ }
822
+
823
+ - (IBAction)flipCameraButtonPressed:(id)sender
824
+ {
825
+ [self.processor performSelector:@selector(flipCamera) withObject:nil afterDelay:0];
826
+ }
827
+
828
+ - (IBAction)torchButtonPressed:(id)sender
829
+ {
830
+ [self.processor performSelector:@selector(toggleTorch) withObject:nil afterDelay:0];
831
+ }
832
+
833
+ //--------------------------------------------------------------------------
834
+ - (UIView *)buildOverlayViewFromXib
835
+ {
836
+ [[NSBundle mainBundle] loadNibNamed:self.alternateXib owner:self options:NULL];
837
+
838
+ if ( self.overlayView == nil )
839
+ {
840
+ NSLog(@"%@", @"An error occurred loading the overlay xib. It appears that the overlayView outlet is not set.");
841
+ return nil;
842
+ }
843
+
844
+ self.overlayView.autoresizesSubviews = YES;
845
+ self.overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
846
+ self.overlayView.opaque = NO;
847
+
848
+ CGRect bounds = self.view.bounds;
849
+ bounds = CGRectMake(0, 0, bounds.size.width, bounds.size.height);
850
+
851
+ [self.overlayView setFrame:bounds];
852
+
853
+ return self.overlayView;
854
+ }
855
+
856
+ //--------------------------------------------------------------------------
857
+ - (UIView*)buildOverlayView {
858
+
859
+ if ( nil != self.alternateXib )
860
+ {
861
+ return [self buildOverlayViewFromXib];
862
+ }
863
+ CGRect bounds = self.view.frame;
864
+ bounds = CGRectMake(0, 0, bounds.size.width, bounds.size.height);
865
+
866
+ UIView* overlayView = [[UIView alloc] initWithFrame:bounds];
867
+ overlayView.autoresizesSubviews = YES;
868
+ overlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
869
+ overlayView.opaque = NO;
870
+
871
+ self.toolbar = [[UIToolbar alloc] init];
872
+ self.toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
873
+
874
+ id cancelButton = [[UIBarButtonItem alloc]
875
+ initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
876
+ target:(id)self
877
+ action:@selector(cancelButtonPressed:)
878
+ ];
879
+
880
+
881
+ id flexSpace = [[UIBarButtonItem alloc]
882
+ initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
883
+ target:nil
884
+ action:nil
885
+ ];
886
+
887
+ id flipCamera = [[UIBarButtonItem alloc]
888
+ initWithBarButtonSystemItem:UIBarButtonSystemItemCamera
889
+ target:(id)self
890
+ action:@selector(flipCameraButtonPressed:)
891
+ ];
892
+
893
+ NSMutableArray *items;
894
+
895
+ #if USE_SHUTTER
896
+ id shutterButton = [[UIBarButtonItem alloc]
897
+ initWithBarButtonSystemItem:UIBarButtonSystemItemCamera
898
+ target:(id)self
899
+ action:@selector(shutterButtonPressed)
900
+ ];
901
+
902
+ if (_processor.isShowFlipCameraButton) {
903
+ items = [NSMutableArray arrayWithObjects:flexSpace, cancelButton, flexSpace, flipCamera, shutterButton, nil];
904
+ } else {
905
+ items = [NSMutableArray arrayWithObjects:flexSpace, cancelButton, flexSpace, shutterButton, nil];
906
+ }
907
+ #else
908
+ if (_processor.isShowFlipCameraButton) {
909
+ items = [@[flexSpace, cancelButton, flexSpace, flipCamera] mutableCopy];
910
+ } else {
911
+ items = [@[flexSpace, cancelButton, flexSpace] mutableCopy];
912
+ }
913
+ #endif
914
+
915
+ if (_processor.isShowTorchButton && !_processor.isFrontCamera) {
916
+ AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
917
+ if ([device hasTorch] && [device hasFlash]) {
918
+ NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"CDVBarcodeScanner" withExtension:@"bundle"];
919
+ NSBundle *bundle = [NSBundle bundleWithURL:bundleURL];
920
+ NSString *imagePath = [bundle pathForResource:@"torch" ofType:@"png"];
921
+ UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
922
+
923
+ id torchButton = [[UIBarButtonItem alloc]
924
+ initWithImage:image
925
+ style:UIBarButtonItemStylePlain
926
+ target:(id)self
927
+ action:@selector(torchButtonPressed:)
928
+ ];
929
+
930
+ [items insertObject:torchButton atIndex:0];
931
+ }
932
+ }
933
+ self.toolbar.items = items;
934
+ [overlayView addSubview: self.toolbar];
935
+
936
+ UIImage* reticleImage = [self buildReticleImage];
937
+ self.reticleView = [[UIImageView alloc] initWithImage:reticleImage];
938
+
939
+ self.reticleView.opaque = NO;
940
+ self.reticleView.contentMode = UIViewContentModeScaleAspectFit;
941
+ self.reticleView.autoresizingMask = (UIViewAutoresizing) (0
942
+ | UIViewAutoresizingFlexibleLeftMargin
943
+ | UIViewAutoresizingFlexibleRightMargin
944
+ | UIViewAutoresizingFlexibleTopMargin
945
+ | UIViewAutoresizingFlexibleBottomMargin)
946
+ ;
947
+
948
+ [overlayView addSubview: self.reticleView];
949
+ [self resizeElements];
950
+ return overlayView;
951
+ }
952
+
953
+ //--------------------------------------------------------------------------
954
+
955
+ #define RETICLE_SIZE 500.0f
956
+ #define RETICLE_WIDTH 10.0f
957
+ #define RETICLE_OFFSET 60.0f
958
+ #define RETICLE_ALPHA 0.4f
959
+
960
+ //-------------------------------------------------------------------------
961
+ // builds the green box and red line
962
+ //-------------------------------------------------------------------------
963
+ - (UIImage*)buildReticleImage {
964
+ UIImage* result;
965
+ UIGraphicsBeginImageContext(CGSizeMake(RETICLE_SIZE, RETICLE_SIZE));
966
+ CGContextRef context = UIGraphicsGetCurrentContext();
967
+
968
+ if (self.processor.is1D) {
969
+ UIColor* color = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:RETICLE_ALPHA];
970
+ CGContextSetStrokeColorWithColor(context, color.CGColor);
971
+ CGContextSetLineWidth(context, RETICLE_WIDTH);
972
+ CGContextBeginPath(context);
973
+ CGFloat lineOffset = (CGFloat) (RETICLE_OFFSET+(0.5*RETICLE_WIDTH));
974
+ CGContextMoveToPoint(context, lineOffset, RETICLE_SIZE/2);
975
+ CGContextAddLineToPoint(context, RETICLE_SIZE-lineOffset, (CGFloat) (0.5*RETICLE_SIZE));
976
+ CGContextStrokePath(context);
977
+ }
978
+
979
+ if (self.processor.is2D) {
980
+ UIColor* color = [UIColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:RETICLE_ALPHA];
981
+ CGContextSetStrokeColorWithColor(context, color.CGColor);
982
+ CGContextSetLineWidth(context, RETICLE_WIDTH);
983
+ CGContextStrokeRect(context,
984
+ CGRectMake(
985
+ RETICLE_OFFSET,
986
+ RETICLE_OFFSET,
987
+ RETICLE_SIZE-2*RETICLE_OFFSET,
988
+ RETICLE_SIZE-2*RETICLE_OFFSET
989
+ )
990
+ );
991
+ }
992
+
993
+ result = UIGraphicsGetImageFromCurrentImageContext();
994
+ UIGraphicsEndImageContext();
995
+ return result;
996
+ }
997
+
998
+ #pragma mark CDVBarcodeScannerOrientationDelegate
999
+
1000
+ - (BOOL)shouldAutorotate
1001
+ {
1002
+ return YES;
1003
+ }
1004
+
1005
+ - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
1006
+ {
1007
+ return [[UIApplication sharedApplication] statusBarOrientation];
1008
+ }
1009
+
1010
+ - (NSUInteger)supportedInterfaceOrientations
1011
+ {
1012
+ return UIInterfaceOrientationMaskAll;
1013
+ }
1014
+
1015
+ - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
1016
+ {
1017
+ if ((self.orientationDelegate != nil) && [self.orientationDelegate respondsToSelector:@selector(shouldAutorotateToInterfaceOrientation:)]) {
1018
+ return [self.orientationDelegate shouldAutorotateToInterfaceOrientation:interfaceOrientation];
1019
+ }
1020
+
1021
+ return YES;
1022
+ }
1023
+
1024
+ - (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
1025
+ {
1026
+ [UIView setAnimationsEnabled:NO];
1027
+ AVCaptureVideoPreviewLayer* previewLayer = self.processor.previewLayer;
1028
+ previewLayer.frame = self.view.bounds;
1029
+
1030
+ if (orientation == UIInterfaceOrientationLandscapeLeft) {
1031
+ [previewLayer setOrientation:AVCaptureVideoOrientationLandscapeLeft];
1032
+ } else if (orientation == UIInterfaceOrientationLandscapeRight) {
1033
+ [previewLayer setOrientation:AVCaptureVideoOrientationLandscapeRight];
1034
+ } else if (orientation == UIInterfaceOrientationPortrait) {
1035
+ [previewLayer setOrientation:AVCaptureVideoOrientationPortrait];
1036
+ } else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
1037
+ [previewLayer setOrientation:AVCaptureVideoOrientationPortraitUpsideDown];
1038
+ }
1039
+
1040
+ previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
1041
+
1042
+ [self resizeElements];
1043
+ [UIView setAnimationsEnabled:YES];
1044
+ }
1045
+
1046
+ -(void) resizeElements {
1047
+ CGRect bounds = self.view.bounds;
1048
+ if (@available(iOS 11.0, *)) {
1049
+ bounds = CGRectMake(bounds.origin.x, bounds.origin.y, bounds.size.width, self.view.safeAreaLayoutGuide.layoutFrame.size.height+self.view.safeAreaLayoutGuide.layoutFrame.origin.y);
1050
+ }
1051
+
1052
+ [self.toolbar sizeToFit];
1053
+ CGFloat toolbarHeight = [self.toolbar frame].size.height;
1054
+ CGFloat rootViewHeight = CGRectGetHeight(bounds);
1055
+ CGFloat rootViewWidth = CGRectGetWidth(bounds);
1056
+ CGRect rectArea = CGRectMake(0, rootViewHeight - toolbarHeight, rootViewWidth, toolbarHeight);
1057
+ [self.toolbar setFrame:rectArea];
1058
+
1059
+ CGFloat minAxis = MIN(rootViewHeight, rootViewWidth);
1060
+
1061
+ rectArea = CGRectMake(
1062
+ (CGFloat) (0.5 * (rootViewWidth - minAxis)),
1063
+ (CGFloat) (0.5 * (rootViewHeight - minAxis)),
1064
+ minAxis,
1065
+ minAxis
1066
+ );
1067
+
1068
+ [self.reticleView setFrame:rectArea];
1069
+ self.reticleView.center = CGPointMake(self.view.center.x, self.view.center.y-self.toolbar.frame.size.height/2);
1070
+ }
1071
+
1072
+ @end