Index: ios/chrome/browser/ui/qr_scanner/qr_scanner_view.mm |
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view.mm b/ios/chrome/browser/ui/qr_scanner/qr_scanner_view.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2101964d24e869103647dbd2bad3b2520181af46 |
--- /dev/null |
+++ b/ios/chrome/browser/ui/qr_scanner/qr_scanner_view.mm |
@@ -0,0 +1,541 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_view.h" |
+ |
+#include "base/logging.h" |
+#include "base/mac/foundation_util.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "ios/chrome/browser/ui/icons/chrome_icon.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#include "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h" |
+#include "ui/base/l10n/l10n_util.h" |
+#include "ui/base/l10n/l10n_util_mac.h" |
+ |
+namespace { |
+// TODO(crbug.com/629427): Replace the temporary UI constants with correct |
+// values for all device types. |
+ |
+// Padding for buttons in the QR scanner UI. |
+const CGFloat kButtonPadding = 16.0; |
+ |
+// Width and height of the QR scanner viewport. |
+const CGFloat kViewportSize_iPhone = 250.0; |
+const CGFloat kViewportSize_iPad = 300.0; |
+ |
+// Length of the viewport borders, starting from the corner. |
+const CGFloat kViewportBorderCornerWidth_iPhone = 25.0; |
+const CGFloat kViewportBorderCornerWidth_iPad = 30.0; |
+ |
+// Opacity of the preview overlay. |
+const CGFloat kPreviewOverlayOpacity = 0.5; |
+ |
+// Corner radius of the border around the viewport. |
+const CGFloat kViewportBorderCornerRadius = 2.0; |
+// Line width of the viewport border. |
+const CGFloat kViewportBorderLineWidth = 4.0; |
+// Shadow opacity of the viewport border. |
+const CGFloat kViewportBorderShadowOpacity = 1.0; |
+// Shadow radius of the viewport border. |
+const CGFloat kViewportBorderShadowRadius = 10.0; |
+// Padding of the viewport caption, below the viewport. |
+const CGFloat kViewportCaptionPadding = 24.0; |
+// Shadow opacity of the viewport caption. |
+const CGFloat kViewportCaptionShadowOpacity = 1.0; |
+// Shadow radius of the viewport caption. |
+const CGFloat kViewportCaptionShadowRadius = 5.0; |
+ |
+// Duration of the flash animation played when a code is scanned. |
+const CGFloat kFlashDuration = 0.5; |
+ |
+// Returns a square of size |rectSize| centered inside |frameSize|. |
+CGRect CenteredRectForViewport(CGSize frameSize, CGFloat rectSize) { |
+ CGFloat rectX = AlignValueToPixel((frameSize.width - rectSize) / 2); |
+ CGFloat rectY = AlignValueToPixel((frameSize.height - rectSize) / 2); |
+ return CGRectMake(rectX, rectY, rectSize, rectSize); |
+} |
+ |
+// Returns the size of the viewport based on the device type. |
+CGFloat GetViewportSize() { |
+ return IsIPadIdiom() ? kViewportSize_iPad : kViewportSize_iPhone; |
+} |
+ |
+} // namespace |
+ |
+// A subclass of UIView with the layerClass property set to |
+// AVCaptureVideoPreviewLayer. Contains the video preview for the QR scanner. |
+@interface VideoPreviewView : UIView |
+ |
+// Returns the VideoPreviewView's layer cast to AVCaptureVideoPreviewLayer. |
+- (AVCaptureVideoPreviewLayer*)previewLayer; |
+ |
+// Returns the rectangle in camera coordinates in which codes should be |
+// recognized. |
+- (CGRect)viewportRectOfInterest; |
+ |
+@end |
+ |
+@implementation VideoPreviewView |
+ |
++ (Class)layerClass { |
+ return [AVCaptureVideoPreviewLayer class]; |
+} |
+ |
+- (AVCaptureVideoPreviewLayer*)previewLayer { |
+ return base::mac::ObjCCastStrict<AVCaptureVideoPreviewLayer>([self layer]); |
+} |
+ |
+- (CGRect)viewportRectOfInterest { |
+ DCHECK(CGPointEqualToPoint(self.frame.origin, CGPointZero)); |
+ CGRect viewportRect = |
+ CenteredRectForViewport(self.frame.size, GetViewportSize()); |
+ AVCaptureVideoPreviewLayer* layer = [self previewLayer]; |
+ // If the layer does not have a connection, |
+ // |metadataOutputRectOfInterestForRect:| does not return the right value. |
+ DCHECK(layer.connection); |
+ return [layer metadataOutputRectOfInterestForRect:viewportRect]; |
+} |
+ |
+@end |
+ |
+// A subclass of UIView containing the preview overlay. It is responsible for |
+// redrawing the preview overlay and the viewport border every time the size |
+// of the preview changes. This UIView should always be square, with its width |
+// and height being the maximum of the width and height of its parent. |
+@interface PreviewOverlayView : UIView { |
+ // Creates a transparent preview overlay. The overlay is a sublayer of the |
+ // PreviewOverlayView's view to keep the opacity of the view's layer 1.0, |
+ // otherwise the viewport border would inherit the opacity of the overlay. |
+ base::scoped_nsobject<CALayer> _previewOverlay; |
+ // A container for the viewport border to draw a shadow under the border. |
+ // Sublayer of PreviewOverlayView's layer. |
+ base::scoped_nsobject<CALayer> _viewportBorderContainer; |
+ // The preview viewport border. Sublayer of |_viewportBorderContainer|. |
+ base::scoped_nsobject<CAShapeLayer> _viewportBorder; |
+} |
+ |
+// Creates a square mask for the overlay to keep the viewport transparent. |
+- (CAShapeLayer*)getViewportMaskWithFrameSize:(CGSize)frameSize |
+ viewportSize:(CGFloat)viewportSize; |
+// Creates a mask to only draw the corners of the viewport border. |
+- (CAShapeLayer*)getViewportBorderMaskWithFrameSize:(CGSize)frameSize |
+ viewportSize:(CGFloat)viewportSize; |
+ |
+@end |
+ |
+@implementation PreviewOverlayView |
+ |
+- (instancetype)initWithFrame:(CGRect)frame { |
+ self = [super initWithFrame:frame]; |
+ if (!self) { |
+ return nil; |
+ } |
+ |
+ _previewOverlay.reset([[CALayer alloc] init]); |
+ [_previewOverlay setBackgroundColor:[[UIColor blackColor] CGColor]]; |
+ [_previewOverlay setOpacity:kPreviewOverlayOpacity]; |
+ [[self layer] addSublayer:_previewOverlay]; |
+ |
+ _viewportBorderContainer.reset([[CALayer alloc] init]); |
+ [_viewportBorderContainer setShadowColor:[[UIColor blackColor] CGColor]]; |
+ [_viewportBorderContainer setShadowOffset:CGSizeZero]; |
+ [_viewportBorderContainer setShadowRadius:kViewportBorderShadowRadius]; |
+ [_viewportBorderContainer setShadowOpacity:kViewportBorderShadowOpacity]; |
+ [_viewportBorderContainer setShouldRasterize:YES]; |
+ [_viewportBorderContainer |
+ setRasterizationScale:[[UIScreen mainScreen] scale]]; |
+ |
+ _viewportBorder.reset([[CAShapeLayer alloc] init]); |
+ [_viewportBorder setStrokeColor:[[UIColor whiteColor] CGColor]]; |
+ [_viewportBorder setFillColor:nil]; |
+ [_viewportBorder setOpacity:1.0]; |
+ [_viewportBorder setLineWidth:kViewportBorderLineWidth]; |
+ [_viewportBorderContainer addSublayer:_viewportBorder]; |
+ |
+ [[self layer] addSublayer:_viewportBorderContainer]; |
+ return self; |
+} |
+ |
+- (void)layoutSubviews { |
+ [super layoutSubviews]; |
+ CGSize frameSize = self.frame.size; |
+ CGFloat viewportSize = GetViewportSize(); |
+ [_previewOverlay |
+ setFrame:CGRectMake(0, 0, frameSize.width, frameSize.height)]; |
+ [_previewOverlay setMask:[self getViewportMaskWithFrameSize:frameSize |
+ viewportSize:viewportSize]]; |
+ |
+ CGRect borderRect = CenteredRectForViewport( |
+ frameSize, viewportSize + kViewportBorderLineWidth); |
+ UIBezierPath* borderPath = |
+ [UIBezierPath bezierPathWithRoundedRect:borderRect |
+ cornerRadius:kViewportBorderCornerRadius]; |
+ |
+ [_viewportBorder setPath:[borderPath CGPath]]; |
+ [_viewportBorder |
+ setMask:[self getViewportBorderMaskWithFrameSize:frameSize |
+ viewportSize:viewportSize]]; |
+} |
+ |
+- (CAShapeLayer*)getViewportMaskWithFrameSize:(CGSize)frameSize |
+ viewportSize:(CGFloat)viewportSize { |
+ CGRect frameRect = CGRectMake(0, 0, frameSize.width, frameSize.height); |
+ CGRect viewportRect = CenteredRectForViewport(frameSize, viewportSize); |
+ UIBezierPath* maskPath = [UIBezierPath bezierPathWithRect:frameRect]; |
+ [maskPath appendPath:[UIBezierPath bezierPathWithRect:viewportRect]]; |
+ |
+ CAShapeLayer* mask = [[[CAShapeLayer alloc] init] autorelease]; |
+ [mask setFillColor:[[UIColor blackColor] CGColor]]; |
+ [mask setFillRule:kCAFillRuleEvenOdd]; |
+ [mask setFrame:frameRect]; |
+ [mask setPath:maskPath.CGPath]; |
+ return mask; |
+} |
+ |
+- (CAShapeLayer*)getViewportBorderMaskWithFrameSize:(CGSize)frameSize |
+ viewportSize:(CGFloat)viewportSize { |
+ CGFloat viewportBorderCornerWidth = IsIPadIdiom() |
+ ? kViewportBorderCornerWidth_iPad |
+ : kViewportBorderCornerWidth_iPhone; |
+ CGRect maskRect = CenteredRectForViewport( |
+ frameSize, viewportSize - 2 * viewportBorderCornerWidth); |
+ CGFloat sizeX = maskRect.origin.x; |
+ CGFloat sizeY = maskRect.origin.y; |
+ CGFloat offsetX = sizeX + maskRect.size.width; |
+ CGFloat offsetY = sizeY + maskRect.size.height; |
+ |
+ UIBezierPath* path = |
+ [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, sizeX, sizeY)]; |
+ [path appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(0, offsetY, |
+ sizeX, sizeY)]]; |
+ [path appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(offsetY, 0, |
+ sizeX, sizeY)]]; |
+ [path appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(offsetX, offsetY, |
+ sizeX, sizeY)]]; |
+ |
+ CAShapeLayer* mask = [[[CAShapeLayer alloc] init] autorelease]; |
+ [mask setFillColor:[[UIColor blackColor] CGColor]]; |
+ [mask setFrame:CGRectMake(0, 0, frameSize.width, frameSize.height)]; |
+ [mask setPath:path.CGPath]; |
+ return mask; |
+} |
+ |
+@end |
+ |
+@interface QRScannerView () { |
+ // A button to toggle the torch. |
+ base::scoped_nsobject<MDCFlatButton> _torchButton; |
+ // A view containing the preview layer for camera input. |
+ base::scoped_nsobject<VideoPreviewView> _previewView; |
+ // A transparent overlay on top of the preview layer. |
+ base::scoped_nsobject<PreviewOverlayView> _previewOverlay; |
+ // The constraint specifying that the preview overlay should be square. |
+ base::scoped_nsobject<NSLayoutConstraint> _overlaySquareConstraint; |
+ // The constraint relating the size of the |_previewOverlay| to the width of |
+ // the QRScannerView. |
+ base::scoped_nsobject<NSLayoutConstraint> _overlayWidthConstraint; |
+ // The constraint relating the size of the |_previewOverlay| to the height of |
+ // te QRScannerView. |
+ base::scoped_nsobject<NSLayoutConstraint> _overlayHeightConstraint; |
+} |
+ |
+// Creates an image with template rendering mode for use in icons. |
+- (UIImage*)templateImageWithName:(NSString*)name; |
+// Creates an icon for torch turned on. |
+- (UIImage*)torchOnIcon; |
+// Creates an icon for torch turned off. |
+- (UIImage*)torchOffIcon; |
+ |
+// Sets common configuration properties of a button in the QR scanner UI and |
+// adds it to self.view. |
+- (void)configureButton:(MDCFlatButton*)button |
+ withIcon:(UIImage*)icon |
+ action:(SEL)action; |
+// Adds a close button. |
+- (void)addCloseButton; |
+// Adds a torch button and stores it in |_torchButton|. |
+- (void)addTorchButton; |
+// Adds a caption to the viewport. |
+- (void)addViewportCaptionLabel; |
+// Adds a preview view to |self| and configures its layout constraints. |
+- (void)setupPreviewView; |
+// Adds a transparent overlay with a viewport border to |self| and configures |
+// its layout constraints. |
+- (void)setupPreviewOverlayView; |
+ |
+@end |
+ |
+@implementation QRScannerView |
+ |
+@synthesize delegate = _delegate; |
+ |
+#pragma mark lifecycle |
+ |
+- (instancetype)initWithFrame:(CGRect)frame |
+ delegate:(id<QRScannerViewDelegate>)delegate { |
+ self = [super initWithFrame:frame]; |
+ if (!self) { |
+ return nil; |
+ } |
+ DCHECK(delegate); |
+ _delegate = delegate; |
+ [self setupPreviewView]; |
+ [self setupPreviewOverlayView]; |
+ [self addCloseButton]; |
+ [self addTorchButton]; |
+ [self addViewportCaptionLabel]; |
+ return self; |
+} |
+ |
+- (instancetype)initWithFrame:(CGRect)frame { |
+ NOTREACHED(); |
+ return nil; |
+} |
+ |
+- (instancetype)initWithCoder:(NSCoder*)coder { |
+ NOTREACHED(); |
+ return nil; |
+} |
+ |
+#pragma mark UIView |
+ |
+// TODO(crbug.com/633577): Replace the preview overlay with a UIView which is |
+// not resized. |
+- (void)layoutSubviews { |
+ [super layoutSubviews]; |
+ [self setBackgroundColor:[UIColor blackColor]]; |
+ if (CGRectEqualToRect([_previewView bounds], CGRectZero)) { |
+ [_previewView setBounds:self.bounds]; |
+ } |
+ [_previewView setCenter:CGPointMake(CGRectGetMidX(self.bounds), |
+ CGRectGetMidY(self.bounds))]; |
+} |
+ |
+#pragma mark public methods |
+ |
+- (AVCaptureVideoPreviewLayer*)getPreviewLayer { |
+ return [_previewView previewLayer]; |
+} |
+ |
+- (void)enableTorchButton:(BOOL)torchIsAvailable { |
+ [_torchButton setEnabled:torchIsAvailable]; |
+ if (!torchIsAvailable) { |
+ [self setTorchButtonTo:NO]; |
+ } |
+} |
+ |
+- (void)setTorchButtonTo:(BOOL)torchIsOn { |
+ DCHECK(_torchButton); |
+ UIImage* icon = nil; |
+ NSString* accessibilityValue = nil; |
+ if (torchIsOn) { |
+ icon = [self torchOnIcon]; |
+ accessibilityValue = |
+ l10n_util::GetNSString(IDS_IOS_QR_SCANNER_TORCH_ON_ACCESSIBILITY_VALUE); |
+ } else { |
+ icon = [self torchOffIcon]; |
+ accessibilityValue = l10n_util::GetNSString( |
+ IDS_IOS_QR_SCANNER_TORCH_OFF_ACCESSIBILITY_VALUE); |
+ } |
+ [_torchButton setImage:icon forState:UIControlStateNormal]; |
+ [_torchButton setAccessibilityValue:accessibilityValue]; |
+} |
+ |
+- (void)resetPreviewFrame:(CGSize)size { |
+ [_previewView setTransform:CGAffineTransformIdentity]; |
+ [_previewView setFrame:CGRectMake(0, 0, size.width, size.height)]; |
+} |
+ |
+- (void)rotatePreviewByAngle:(CGFloat)angle { |
+ [_previewView |
+ setTransform:CGAffineTransformRotate([_previewView transform], angle)]; |
+} |
+ |
+- (void)finishPreviewRotation { |
+ CGAffineTransform rotation = [_previewView transform]; |
+ // Check that the current transform is either an identity or a 90, -90, or 180 |
+ // degree rotation. |
+ DCHECK(fabs(atan2f(rotation.b, rotation.a)) < 0.001 || |
+ fabs(fabs(atan2f(rotation.b, rotation.a)) - M_PI) < 0.001 || |
+ fabs(fabs(atan2f(rotation.b, rotation.a)) - M_PI / 2) < 0.001); |
+ rotation.a = round(rotation.a); |
+ rotation.b = round(rotation.b); |
+ rotation.c = round(rotation.c); |
+ rotation.d = round(rotation.d); |
+ [_previewView setTransform:rotation]; |
+} |
+ |
+- (CGRect)viewportRectOfInterest { |
+ return [_previewView viewportRectOfInterest]; |
+} |
+ |
+- (void)animateScanningResultWithCompletion:(void (^)(void))completion { |
+ UIView* whiteView = [[[UIView alloc] init] autorelease]; |
+ whiteView.frame = self.bounds; |
+ [self addSubview:whiteView]; |
+ whiteView.backgroundColor = [UIColor whiteColor]; |
+ [UIView animateWithDuration:kFlashDuration |
+ animations:^{ |
+ whiteView.alpha = 0.0; |
+ } |
+ completion:^void(BOOL finished) { |
+ [whiteView removeFromSuperview]; |
+ if (completion) { |
+ completion(); |
+ } |
+ }]; |
+} |
+ |
+#pragma mark private methods |
+ |
+- (UIImage*)templateImageWithName:(NSString*)name { |
+ UIImage* image = [[UIImage imageNamed:name] |
+ imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
+ DCHECK(image); |
+ return image; |
+} |
+ |
+- (UIImage*)torchOnIcon { |
+ UIImage* icon = [self templateImageWithName:@"qr_scanner_torch_on"]; |
+ return icon; |
+} |
+ |
+- (UIImage*)torchOffIcon { |
+ UIImage* icon = [self templateImageWithName:@"qr_scanner_torch_off"]; |
+ return icon; |
+} |
+ |
+- (void)configureButton:(MDCFlatButton*)button |
+ withIcon:(UIImage*)icon |
+ action:(SEL)action { |
+ [button setTintColor:[UIColor whiteColor]]; |
+ [button setImage:icon forState:UIControlStateNormal]; |
+ [button setInkStyle:MDCInkStyleUnbounded]; |
+ [button addTarget:_delegate |
+ action:action |
+ forControlEvents:UIControlEventTouchUpInside]; |
+ [self addSubview:button]; |
+} |
+ |
+- (void)addCloseButton { |
+ MDCFlatButton* closeButton = |
+ [[[MDCFlatButton alloc] initWithFrame:CGRectZero] autorelease]; |
+ UIImage* closeIcon = [ChromeIcon closeIcon]; |
+ UIImage* closeButtonIcon = |
+ [closeIcon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
+ [closeButton setAccessibilityLabel:[closeIcon accessibilityLabel]]; |
+ [closeButton setAccessibilityIdentifier:[closeIcon accessibilityIdentifier]]; |
+ [self configureButton:closeButton |
+ withIcon:closeButtonIcon |
+ action:@selector(dismissQRScannerView:)]; |
+ |
+ // Constraints for closeButton. |
+ [closeButton setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [NSLayoutConstraint activateConstraints:@[ |
+ [[closeButton leadingAnchor] constraintEqualToAnchor:[self leadingAnchor] |
+ constant:kButtonPadding], |
+ [[closeButton bottomAnchor] constraintEqualToAnchor:[self bottomAnchor] |
+ constant:-kButtonPadding] |
+ ]]; |
+} |
+ |
+- (void)addTorchButton { |
+ DCHECK(!_torchButton); |
+ _torchButton.reset([[MDCFlatButton alloc] initWithFrame:CGRectZero]); |
+ [_torchButton setEnabled:NO]; |
+ [self configureButton:_torchButton |
+ withIcon:[self torchOffIcon] |
+ action:@selector(toggleTorch:)]; |
+ [_torchButton setAccessibilityIdentifier:@"qr_scanner_torch_button"]; |
+ [_torchButton setAccessibilityLabel: |
+ l10n_util::GetNSString( |
+ IDS_IOS_QR_SCANNER_TORCH_BUTTON_ACCESSIBILITY_LABEL)]; |
+ [_torchButton setAccessibilityValue: |
+ l10n_util::GetNSString( |
+ IDS_IOS_QR_SCANNER_TORCH_OFF_ACCESSIBILITY_VALUE)]; |
+ |
+ // Constraints for _torchButton. |
+ [_torchButton setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [NSLayoutConstraint activateConstraints:@[ |
+ [[_torchButton trailingAnchor] constraintEqualToAnchor:[self trailingAnchor] |
+ constant:-kButtonPadding], |
+ [[_torchButton bottomAnchor] constraintEqualToAnchor:[self bottomAnchor] |
+ constant:-kButtonPadding] |
+ ]]; |
+} |
+ |
+- (void)addViewportCaptionLabel { |
+ UILabel* viewportCaption = [[[UILabel alloc] init] autorelease]; |
+ NSString* label = l10n_util::GetNSString(IDS_IOS_QR_SCANNER_VIEWPORT_CAPTION); |
+ [viewportCaption setText:label]; |
+ [viewportCaption setAccessibilityLabel:label]; |
+ [viewportCaption setAccessibilityIdentifier:@"qr_scanner_viewport_caption"]; |
+ [viewportCaption setTextColor:[UIColor whiteColor]]; |
+ [viewportCaption.layer setShadowColor:[UIColor blackColor].CGColor]; |
+ [viewportCaption.layer setShadowOffset:CGSizeZero]; |
+ [viewportCaption.layer setShadowRadius:kViewportCaptionShadowRadius]; |
+ [viewportCaption.layer setShadowOpacity:kViewportCaptionShadowOpacity]; |
+ [viewportCaption.layer setMasksToBounds:NO]; |
+ [viewportCaption.layer setShouldRasterize:YES]; |
+ [self addSubview:viewportCaption]; |
+ |
+ // Constraints for viewportCaption. |
+ [viewportCaption setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [NSLayoutConstraint activateConstraints:@[ |
+ [[viewportCaption centerXAnchor] |
+ constraintEqualToAnchor:[self centerXAnchor]], |
+ [[viewportCaption centerYAnchor] |
+ constraintEqualToAnchor:[self centerYAnchor] |
+ constant:GetViewportSize() / 2 + kViewportCaptionPadding] |
+ ]]; |
+} |
+ |
+- (void)setupPreviewView { |
+ DCHECK(!_previewView); |
+ _previewView.reset([[VideoPreviewView alloc] initWithFrame:self.frame]); |
+ [self insertSubview:_previewView atIndex:0]; |
+} |
+ |
+- (void)setupPreviewOverlayView { |
+ DCHECK(!_previewOverlay); |
+ _previewOverlay.reset([[PreviewOverlayView alloc] initWithFrame:CGRectZero]); |
+ [self addSubview:_previewOverlay]; |
+ |
+ // Add a multiplier of sqrt(2) to the width and height constraints to make |
+ // sure that the overlay covers the whole screen during rotation. |
+ _overlayWidthConstraint.reset( |
+ [[NSLayoutConstraint constraintWithItem:_previewOverlay |
+ attribute:NSLayoutAttributeWidth |
+ relatedBy:NSLayoutRelationGreaterThanOrEqual |
+ toItem:self |
+ attribute:NSLayoutAttributeWidth |
+ multiplier:sqrt(2) |
+ constant:0.0] retain]); |
+ |
+ _overlayHeightConstraint.reset( |
+ [[NSLayoutConstraint constraintWithItem:_previewOverlay |
+ attribute:NSLayoutAttributeHeight |
+ relatedBy:NSLayoutRelationGreaterThanOrEqual |
+ toItem:self |
+ attribute:NSLayoutAttributeHeight |
+ multiplier:sqrt(2) |
+ constant:0.0] retain]); |
+ |
+ _overlaySquareConstraint.reset([[[_previewOverlay heightAnchor] |
+ constraintEqualToAnchor:[_previewOverlay widthAnchor]] retain]); |
+ |
+ // Constrains the preview overlay to be square, centered, with both width and |
+ // height greater than or equal to the width and height of the QRScannerView. |
+ [_previewOverlay setTranslatesAutoresizingMaskIntoConstraints:NO]; |
+ [NSLayoutConstraint activateConstraints:@[ |
+ [[_previewOverlay centerXAnchor] |
+ constraintEqualToAnchor:[self centerXAnchor]], |
+ [[_previewOverlay centerYAnchor] |
+ constraintEqualToAnchor:[self centerYAnchor]], |
+ _overlaySquareConstraint, _overlayWidthConstraint, _overlayHeightConstraint |
+ ]]; |
+} |
+ |
+@end |