session-ios/Signal/src/view controllers/OWSQRCodeScanningViewController.m
Matthew Douglass bbfffdf79f Scanning an invalid QR code shows an error dialog
* From the error dialog you are able to back out of scanning or try again. Adds English localization for this message.

  Adds an extra flag in OWSQRCodeScanningViewController to make sure that we don’t handle captureResults when capture is disabled (this was racing before because the call to [capture stop] is async and that was causing the cancel case to present the error dialog multiple times.

  Fixes Signal-iOS#1347

* Fixes appearance of race with starting capture
  Also marks the capture variable as atomic since it is accessed on
  multiple threads.

// FREEBIE
2016-10-26 15:49:19 -04:00

179 lines
4.6 KiB
Objective-C

// Copyright © 2016 Open Whisper Systems. All rights reserved.
#import "OWSQRCodeScanningViewController.h"
#import "UIColor+OWS.h"
//#import <ZXingObjC/ZXingObjC.h>
@interface OWSQRCodeScanningViewController ()
@property (nonatomic) BOOL captureEnabled;
@property (atomic, strong) ZXCapture *capture;
@property UIView *maskingView;
@property CALayer *maskingLayer;
@end
@implementation OWSQRCodeScanningViewController
- (void)dealloc
{
[self.capture.layer removeFromSuperlayer];
}
- (instancetype)init
{
self = [super init];
if (!self) {
return self;
}
_captureEnabled = NO;
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (!self) {
return self;
}
_captureEnabled = NO;
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.maskingView = [[UIView alloc] initWithFrame:self.view.frame];
[self.view addSubview:self.maskingView];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.captureEnabled) {
[self startCapture];
}
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self layoutMaskingView];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self stopCapture];
}
- (void)layoutMaskingView
{
self.maskingView.frame = self.view.frame;
if (self.maskingLayer) {
[self.maskingLayer removeFromSuperlayer];
}
self.maskingLayer = [self buildCircularMaskingLayer];
[self.maskingView.layer addSublayer:self.maskingLayer];
}
- (void)startCapture
{
self.captureEnabled = YES;
if (!self.capture) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.capture = [[ZXCapture alloc] init];
self.capture.camera = self.capture.back;
self.capture.focusMode = AVCaptureFocusModeContinuousAutoFocus;
self.capture.layer.frame = self.view.frame;
self.capture.delegate = self;
dispatch_async(dispatch_get_main_queue(), ^{
[self.view.layer addSublayer:self.capture.layer];
[self.view bringSubviewToFront:self.maskingView];
[self.capture start];
});
});
} else {
[self.capture start];
}
}
- (void)stopCapture
{
self.captureEnabled = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.capture stop];
});
}
- (CAShapeLayer *)buildCircularMaskingLayer
{
// Add a circular mask
UIBezierPath *path = [UIBezierPath
bezierPathWithRoundedRect:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)
cornerRadius:0];
CGFloat verticalMargin = 8.0;
CGFloat radius = self.view.frame.size.height / 2.0f - verticalMargin;
// Center the circle's bounding rectangle
CGFloat horizontalMargin = (self.view.frame.size.width - 2.0f * radius) / 2.0f;
UIBezierPath *circlePath = [UIBezierPath
bezierPathWithRoundedRect:CGRectMake(horizontalMargin, verticalMargin, 2.0f * radius, 2.0f * radius)
cornerRadius:radius];
[path appendPath:circlePath];
[path setUsesEvenOddFillRule:YES];
CAShapeLayer *fillLayer = [CAShapeLayer layer];
fillLayer.path = path.CGPath;
fillLayer.fillRule = kCAFillRuleEvenOdd;
fillLayer.fillColor = [UIColor grayColor].CGColor;
fillLayer.opacity = 0.5;
return fillLayer;
}
- (void)captureResult:(ZXCapture *)capture result:(ZXResult *)result
{
if (!self.captureEnabled)
return;
[self stopCapture];
// TODO bounding rectangle
// Vibrate
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
if (self.scanDelegate) {
if ([self.scanDelegate respondsToSelector:@selector(controller:didDetectQRCodeWithData:)]) {
DDLogInfo(@"%@ Scanned Data Code.", self.tag);
ZXByteArray *byteArray = result.resultMetadata[@(kResultMetadataTypeByteSegments)][0];
NSData *decodedData = [NSData dataWithBytes:byteArray.array length:byteArray.length];
[self.scanDelegate controller:self didDetectQRCodeWithData:decodedData];
}
if ([self.scanDelegate respondsToSelector:@selector(controller:didDetectQRCodeWithString:)]) {
DDLogInfo(@"%@ Scanned String Code.", self.tag);
[self.scanDelegate controller:self didDetectQRCodeWithString:result.text];
}
}
}
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end