| 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
|
|
|