OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #import "ios/chrome/browser/ui/qr_scanner/qr_scanner_view.h" |
| 6 |
| 7 #include "base/logging.h" |
| 8 #include "base/mac/foundation_util.h" |
| 9 #include "base/mac/scoped_nsobject.h" |
| 10 #include "ios/chrome/browser/ui/icons/chrome_icon.h" |
| 11 #include "ios/chrome/browser/ui/ui_util.h" |
| 12 #include "ios/chrome/grit/ios_strings.h" |
| 13 #include "ios/third_party/material_components_ios/src/components/Buttons/src/Mat
erialButtons.h" |
| 14 #include "ui/base/l10n/l10n_util.h" |
| 15 #include "ui/base/l10n/l10n_util_mac.h" |
| 16 |
| 17 namespace { |
| 18 // TODO(crbug.com/629427): Replace the temporary UI constants with correct |
| 19 // values for all device types. |
| 20 |
| 21 // Padding for buttons in the QR scanner UI. |
| 22 const CGFloat kButtonPadding = 16.0; |
| 23 |
| 24 // Width and height of the QR scanner viewport. |
| 25 const CGFloat kViewportSize_iPhone = 250.0; |
| 26 const CGFloat kViewportSize_iPad = 300.0; |
| 27 |
| 28 // Length of the viewport borders, starting from the corner. |
| 29 const CGFloat kViewportBorderCornerWidth_iPhone = 25.0; |
| 30 const CGFloat kViewportBorderCornerWidth_iPad = 30.0; |
| 31 |
| 32 // Opacity of the preview overlay. |
| 33 const CGFloat kPreviewOverlayOpacity = 0.5; |
| 34 |
| 35 // Corner radius of the border around the viewport. |
| 36 const CGFloat kViewportBorderCornerRadius = 2.0; |
| 37 // Line width of the viewport border. |
| 38 const CGFloat kViewportBorderLineWidth = 4.0; |
| 39 // Shadow opacity of the viewport border. |
| 40 const CGFloat kViewportBorderShadowOpacity = 1.0; |
| 41 // Shadow radius of the viewport border. |
| 42 const CGFloat kViewportBorderShadowRadius = 10.0; |
| 43 // Padding of the viewport caption, below the viewport. |
| 44 const CGFloat kViewportCaptionPadding = 24.0; |
| 45 // Shadow opacity of the viewport caption. |
| 46 const CGFloat kViewportCaptionShadowOpacity = 1.0; |
| 47 // Shadow radius of the viewport caption. |
| 48 const CGFloat kViewportCaptionShadowRadius = 5.0; |
| 49 |
| 50 // Duration of the flash animation played when a code is scanned. |
| 51 const CGFloat kFlashDuration = 0.5; |
| 52 |
| 53 // Returns a square of size |rectSize| centered inside |frameSize|. |
| 54 CGRect CenteredRectForViewport(CGSize frameSize, CGFloat rectSize) { |
| 55 CGFloat rectX = AlignValueToPixel((frameSize.width - rectSize) / 2); |
| 56 CGFloat rectY = AlignValueToPixel((frameSize.height - rectSize) / 2); |
| 57 return CGRectMake(rectX, rectY, rectSize, rectSize); |
| 58 } |
| 59 |
| 60 // Returns the size of the viewport based on the device type. |
| 61 CGFloat GetViewportSize() { |
| 62 return IsIPadIdiom() ? kViewportSize_iPad : kViewportSize_iPhone; |
| 63 } |
| 64 |
| 65 } // namespace |
| 66 |
| 67 // A subclass of UIView with the layerClass property set to |
| 68 // AVCaptureVideoPreviewLayer. Contains the video preview for the QR scanner. |
| 69 @interface VideoPreviewView : UIView |
| 70 |
| 71 // Returns the VideoPreviewView's layer cast to AVCaptureVideoPreviewLayer. |
| 72 - (AVCaptureVideoPreviewLayer*)previewLayer; |
| 73 |
| 74 // Returns the rectangle in camera coordinates in which codes should be |
| 75 // recognized. |
| 76 - (CGRect)viewportRectOfInterest; |
| 77 |
| 78 @end |
| 79 |
| 80 @implementation VideoPreviewView |
| 81 |
| 82 + (Class)layerClass { |
| 83 return [AVCaptureVideoPreviewLayer class]; |
| 84 } |
| 85 |
| 86 - (AVCaptureVideoPreviewLayer*)previewLayer { |
| 87 return base::mac::ObjCCastStrict<AVCaptureVideoPreviewLayer>([self layer]); |
| 88 } |
| 89 |
| 90 - (CGRect)viewportRectOfInterest { |
| 91 DCHECK(CGPointEqualToPoint(self.frame.origin, CGPointZero)); |
| 92 CGRect viewportRect = |
| 93 CenteredRectForViewport(self.frame.size, GetViewportSize()); |
| 94 AVCaptureVideoPreviewLayer* layer = [self previewLayer]; |
| 95 // If the layer does not have a connection, |
| 96 // |metadataOutputRectOfInterestForRect:| does not return the right value. |
| 97 DCHECK(layer.connection); |
| 98 return [layer metadataOutputRectOfInterestForRect:viewportRect]; |
| 99 } |
| 100 |
| 101 @end |
| 102 |
| 103 // A subclass of UIView containing the preview overlay. It is responsible for |
| 104 // redrawing the preview overlay and the viewport border every time the size |
| 105 // of the preview changes. This UIView should always be square, with its width |
| 106 // and height being the maximum of the width and height of its parent. |
| 107 @interface PreviewOverlayView : UIView { |
| 108 // Creates a transparent preview overlay. The overlay is a sublayer of the |
| 109 // PreviewOverlayView's view to keep the opacity of the view's layer 1.0, |
| 110 // otherwise the viewport border would inherit the opacity of the overlay. |
| 111 base::scoped_nsobject<CALayer> _previewOverlay; |
| 112 // A container for the viewport border to draw a shadow under the border. |
| 113 // Sublayer of PreviewOverlayView's layer. |
| 114 base::scoped_nsobject<CALayer> _viewportBorderContainer; |
| 115 // The preview viewport border. Sublayer of |_viewportBorderContainer|. |
| 116 base::scoped_nsobject<CAShapeLayer> _viewportBorder; |
| 117 } |
| 118 |
| 119 // Creates a square mask for the overlay to keep the viewport transparent. |
| 120 - (CAShapeLayer*)getViewportMaskWithFrameSize:(CGSize)frameSize |
| 121 viewportSize:(CGFloat)viewportSize; |
| 122 // Creates a mask to only draw the corners of the viewport border. |
| 123 - (CAShapeLayer*)getViewportBorderMaskWithFrameSize:(CGSize)frameSize |
| 124 viewportSize:(CGFloat)viewportSize; |
| 125 |
| 126 @end |
| 127 |
| 128 @implementation PreviewOverlayView |
| 129 |
| 130 - (instancetype)initWithFrame:(CGRect)frame { |
| 131 self = [super initWithFrame:frame]; |
| 132 if (!self) { |
| 133 return nil; |
| 134 } |
| 135 |
| 136 _previewOverlay.reset([[CALayer alloc] init]); |
| 137 [_previewOverlay setBackgroundColor:[[UIColor blackColor] CGColor]]; |
| 138 [_previewOverlay setOpacity:kPreviewOverlayOpacity]; |
| 139 [[self layer] addSublayer:_previewOverlay]; |
| 140 |
| 141 _viewportBorderContainer.reset([[CALayer alloc] init]); |
| 142 [_viewportBorderContainer setShadowColor:[[UIColor blackColor] CGColor]]; |
| 143 [_viewportBorderContainer setShadowOffset:CGSizeZero]; |
| 144 [_viewportBorderContainer setShadowRadius:kViewportBorderShadowRadius]; |
| 145 [_viewportBorderContainer setShadowOpacity:kViewportBorderShadowOpacity]; |
| 146 [_viewportBorderContainer setShouldRasterize:YES]; |
| 147 [_viewportBorderContainer |
| 148 setRasterizationScale:[[UIScreen mainScreen] scale]]; |
| 149 |
| 150 _viewportBorder.reset([[CAShapeLayer alloc] init]); |
| 151 [_viewportBorder setStrokeColor:[[UIColor whiteColor] CGColor]]; |
| 152 [_viewportBorder setFillColor:nil]; |
| 153 [_viewportBorder setOpacity:1.0]; |
| 154 [_viewportBorder setLineWidth:kViewportBorderLineWidth]; |
| 155 [_viewportBorderContainer addSublayer:_viewportBorder]; |
| 156 |
| 157 [[self layer] addSublayer:_viewportBorderContainer]; |
| 158 return self; |
| 159 } |
| 160 |
| 161 - (void)layoutSubviews { |
| 162 [super layoutSubviews]; |
| 163 CGSize frameSize = self.frame.size; |
| 164 CGFloat viewportSize = GetViewportSize(); |
| 165 [_previewOverlay |
| 166 setFrame:CGRectMake(0, 0, frameSize.width, frameSize.height)]; |
| 167 [_previewOverlay setMask:[self getViewportMaskWithFrameSize:frameSize |
| 168 viewportSize:viewportSize]]; |
| 169 |
| 170 CGRect borderRect = CenteredRectForViewport( |
| 171 frameSize, viewportSize + kViewportBorderLineWidth); |
| 172 UIBezierPath* borderPath = |
| 173 [UIBezierPath bezierPathWithRoundedRect:borderRect |
| 174 cornerRadius:kViewportBorderCornerRadius]; |
| 175 |
| 176 [_viewportBorder setPath:[borderPath CGPath]]; |
| 177 [_viewportBorder |
| 178 setMask:[self getViewportBorderMaskWithFrameSize:frameSize |
| 179 viewportSize:viewportSize]]; |
| 180 } |
| 181 |
| 182 - (CAShapeLayer*)getViewportMaskWithFrameSize:(CGSize)frameSize |
| 183 viewportSize:(CGFloat)viewportSize { |
| 184 CGRect frameRect = CGRectMake(0, 0, frameSize.width, frameSize.height); |
| 185 CGRect viewportRect = CenteredRectForViewport(frameSize, viewportSize); |
| 186 UIBezierPath* maskPath = [UIBezierPath bezierPathWithRect:frameRect]; |
| 187 [maskPath appendPath:[UIBezierPath bezierPathWithRect:viewportRect]]; |
| 188 |
| 189 CAShapeLayer* mask = [[[CAShapeLayer alloc] init] autorelease]; |
| 190 [mask setFillColor:[[UIColor blackColor] CGColor]]; |
| 191 [mask setFillRule:kCAFillRuleEvenOdd]; |
| 192 [mask setFrame:frameRect]; |
| 193 [mask setPath:maskPath.CGPath]; |
| 194 return mask; |
| 195 } |
| 196 |
| 197 - (CAShapeLayer*)getViewportBorderMaskWithFrameSize:(CGSize)frameSize |
| 198 viewportSize:(CGFloat)viewportSize { |
| 199 CGFloat viewportBorderCornerWidth = IsIPadIdiom() |
| 200 ? kViewportBorderCornerWidth_iPad |
| 201 : kViewportBorderCornerWidth_iPhone; |
| 202 CGRect maskRect = CenteredRectForViewport( |
| 203 frameSize, viewportSize - 2 * viewportBorderCornerWidth); |
| 204 CGFloat sizeX = maskRect.origin.x; |
| 205 CGFloat sizeY = maskRect.origin.y; |
| 206 CGFloat offsetX = sizeX + maskRect.size.width; |
| 207 CGFloat offsetY = sizeY + maskRect.size.height; |
| 208 |
| 209 UIBezierPath* path = |
| 210 [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, sizeX, sizeY)]; |
| 211 [path appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(0, offsetY, |
| 212 sizeX, sizeY)]]; |
| 213 [path appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(offsetY, 0, |
| 214 sizeX, sizeY)]]; |
| 215 [path appendPath:[UIBezierPath bezierPathWithRect:CGRectMake(offsetX, offsetY, |
| 216 sizeX, sizeY)]]; |
| 217 |
| 218 CAShapeLayer* mask = [[[CAShapeLayer alloc] init] autorelease]; |
| 219 [mask setFillColor:[[UIColor blackColor] CGColor]]; |
| 220 [mask setFrame:CGRectMake(0, 0, frameSize.width, frameSize.height)]; |
| 221 [mask setPath:path.CGPath]; |
| 222 return mask; |
| 223 } |
| 224 |
| 225 @end |
| 226 |
| 227 @interface QRScannerView () { |
| 228 // A button to toggle the torch. |
| 229 base::scoped_nsobject<MDCFlatButton> _torchButton; |
| 230 // A view containing the preview layer for camera input. |
| 231 base::scoped_nsobject<VideoPreviewView> _previewView; |
| 232 // A transparent overlay on top of the preview layer. |
| 233 base::scoped_nsobject<PreviewOverlayView> _previewOverlay; |
| 234 // The constraint specifying that the preview overlay should be square. |
| 235 base::scoped_nsobject<NSLayoutConstraint> _overlaySquareConstraint; |
| 236 // The constraint relating the size of the |_previewOverlay| to the width of |
| 237 // the QRScannerView. |
| 238 base::scoped_nsobject<NSLayoutConstraint> _overlayWidthConstraint; |
| 239 // The constraint relating the size of the |_previewOverlay| to the height of |
| 240 // te QRScannerView. |
| 241 base::scoped_nsobject<NSLayoutConstraint> _overlayHeightConstraint; |
| 242 } |
| 243 |
| 244 // Creates an image with template rendering mode for use in icons. |
| 245 - (UIImage*)templateImageWithName:(NSString*)name; |
| 246 // Creates an icon for torch turned on. |
| 247 - (UIImage*)torchOnIcon; |
| 248 // Creates an icon for torch turned off. |
| 249 - (UIImage*)torchOffIcon; |
| 250 |
| 251 // Sets common configuration properties of a button in the QR scanner UI and |
| 252 // adds it to self.view. |
| 253 - (void)configureButton:(MDCFlatButton*)button |
| 254 withIcon:(UIImage*)icon |
| 255 action:(SEL)action; |
| 256 // Adds a close button. |
| 257 - (void)addCloseButton; |
| 258 // Adds a torch button and stores it in |_torchButton|. |
| 259 - (void)addTorchButton; |
| 260 // Adds a caption to the viewport. |
| 261 - (void)addViewportCaptionLabel; |
| 262 // Adds a preview view to |self| and configures its layout constraints. |
| 263 - (void)setupPreviewView; |
| 264 // Adds a transparent overlay with a viewport border to |self| and configures |
| 265 // its layout constraints. |
| 266 - (void)setupPreviewOverlayView; |
| 267 |
| 268 @end |
| 269 |
| 270 @implementation QRScannerView |
| 271 |
| 272 @synthesize delegate = _delegate; |
| 273 |
| 274 #pragma mark lifecycle |
| 275 |
| 276 - (instancetype)initWithFrame:(CGRect)frame |
| 277 delegate:(id<QRScannerViewDelegate>)delegate { |
| 278 self = [super initWithFrame:frame]; |
| 279 if (!self) { |
| 280 return nil; |
| 281 } |
| 282 DCHECK(delegate); |
| 283 _delegate = delegate; |
| 284 [self setupPreviewView]; |
| 285 [self setupPreviewOverlayView]; |
| 286 [self addCloseButton]; |
| 287 [self addTorchButton]; |
| 288 [self addViewportCaptionLabel]; |
| 289 return self; |
| 290 } |
| 291 |
| 292 - (instancetype)initWithFrame:(CGRect)frame { |
| 293 NOTREACHED(); |
| 294 return nil; |
| 295 } |
| 296 |
| 297 - (instancetype)initWithCoder:(NSCoder*)coder { |
| 298 NOTREACHED(); |
| 299 return nil; |
| 300 } |
| 301 |
| 302 #pragma mark UIView |
| 303 |
| 304 // TODO(crbug.com/633577): Replace the preview overlay with a UIView which is |
| 305 // not resized. |
| 306 - (void)layoutSubviews { |
| 307 [super layoutSubviews]; |
| 308 [self setBackgroundColor:[UIColor blackColor]]; |
| 309 if (CGRectEqualToRect([_previewView bounds], CGRectZero)) { |
| 310 [_previewView setBounds:self.bounds]; |
| 311 } |
| 312 [_previewView setCenter:CGPointMake(CGRectGetMidX(self.bounds), |
| 313 CGRectGetMidY(self.bounds))]; |
| 314 } |
| 315 |
| 316 #pragma mark public methods |
| 317 |
| 318 - (AVCaptureVideoPreviewLayer*)getPreviewLayer { |
| 319 return [_previewView previewLayer]; |
| 320 } |
| 321 |
| 322 - (void)enableTorchButton:(BOOL)torchIsAvailable { |
| 323 [_torchButton setEnabled:torchIsAvailable]; |
| 324 if (!torchIsAvailable) { |
| 325 [self setTorchButtonTo:NO]; |
| 326 } |
| 327 } |
| 328 |
| 329 - (void)setTorchButtonTo:(BOOL)torchIsOn { |
| 330 DCHECK(_torchButton); |
| 331 UIImage* icon = nil; |
| 332 NSString* accessibilityValue = nil; |
| 333 if (torchIsOn) { |
| 334 icon = [self torchOnIcon]; |
| 335 accessibilityValue = |
| 336 l10n_util::GetNSString(IDS_IOS_QR_SCANNER_TORCH_ON_ACCESSIBILITY_VALUE); |
| 337 } else { |
| 338 icon = [self torchOffIcon]; |
| 339 accessibilityValue = l10n_util::GetNSString( |
| 340 IDS_IOS_QR_SCANNER_TORCH_OFF_ACCESSIBILITY_VALUE); |
| 341 } |
| 342 [_torchButton setImage:icon forState:UIControlStateNormal]; |
| 343 [_torchButton setAccessibilityValue:accessibilityValue]; |
| 344 } |
| 345 |
| 346 - (void)resetPreviewFrame:(CGSize)size { |
| 347 [_previewView setTransform:CGAffineTransformIdentity]; |
| 348 [_previewView setFrame:CGRectMake(0, 0, size.width, size.height)]; |
| 349 } |
| 350 |
| 351 - (void)rotatePreviewByAngle:(CGFloat)angle { |
| 352 [_previewView |
| 353 setTransform:CGAffineTransformRotate([_previewView transform], angle)]; |
| 354 } |
| 355 |
| 356 - (void)finishPreviewRotation { |
| 357 CGAffineTransform rotation = [_previewView transform]; |
| 358 // Check that the current transform is either an identity or a 90, -90, or 180 |
| 359 // degree rotation. |
| 360 DCHECK(fabs(atan2f(rotation.b, rotation.a)) < 0.001 || |
| 361 fabs(fabs(atan2f(rotation.b, rotation.a)) - M_PI) < 0.001 || |
| 362 fabs(fabs(atan2f(rotation.b, rotation.a)) - M_PI / 2) < 0.001); |
| 363 rotation.a = round(rotation.a); |
| 364 rotation.b = round(rotation.b); |
| 365 rotation.c = round(rotation.c); |
| 366 rotation.d = round(rotation.d); |
| 367 [_previewView setTransform:rotation]; |
| 368 } |
| 369 |
| 370 - (CGRect)viewportRectOfInterest { |
| 371 return [_previewView viewportRectOfInterest]; |
| 372 } |
| 373 |
| 374 - (void)animateScanningResultWithCompletion:(void (^)(void))completion { |
| 375 UIView* whiteView = [[[UIView alloc] init] autorelease]; |
| 376 whiteView.frame = self.bounds; |
| 377 [self addSubview:whiteView]; |
| 378 whiteView.backgroundColor = [UIColor whiteColor]; |
| 379 [UIView animateWithDuration:kFlashDuration |
| 380 animations:^{ |
| 381 whiteView.alpha = 0.0; |
| 382 } |
| 383 completion:^void(BOOL finished) { |
| 384 [whiteView removeFromSuperview]; |
| 385 if (completion) { |
| 386 completion(); |
| 387 } |
| 388 }]; |
| 389 } |
| 390 |
| 391 #pragma mark private methods |
| 392 |
| 393 - (UIImage*)templateImageWithName:(NSString*)name { |
| 394 UIImage* image = [[UIImage imageNamed:name] |
| 395 imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| 396 DCHECK(image); |
| 397 return image; |
| 398 } |
| 399 |
| 400 - (UIImage*)torchOnIcon { |
| 401 UIImage* icon = [self templateImageWithName:@"qr_scanner_torch_on"]; |
| 402 return icon; |
| 403 } |
| 404 |
| 405 - (UIImage*)torchOffIcon { |
| 406 UIImage* icon = [self templateImageWithName:@"qr_scanner_torch_off"]; |
| 407 return icon; |
| 408 } |
| 409 |
| 410 - (void)configureButton:(MDCFlatButton*)button |
| 411 withIcon:(UIImage*)icon |
| 412 action:(SEL)action { |
| 413 [button setTintColor:[UIColor whiteColor]]; |
| 414 [button setImage:icon forState:UIControlStateNormal]; |
| 415 [button setInkStyle:MDCInkStyleUnbounded]; |
| 416 [button addTarget:_delegate |
| 417 action:action |
| 418 forControlEvents:UIControlEventTouchUpInside]; |
| 419 [self addSubview:button]; |
| 420 } |
| 421 |
| 422 - (void)addCloseButton { |
| 423 MDCFlatButton* closeButton = |
| 424 [[[MDCFlatButton alloc] initWithFrame:CGRectZero] autorelease]; |
| 425 UIImage* closeIcon = [ChromeIcon closeIcon]; |
| 426 UIImage* closeButtonIcon = |
| 427 [closeIcon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; |
| 428 [closeButton setAccessibilityLabel:[closeIcon accessibilityLabel]]; |
| 429 [closeButton setAccessibilityIdentifier:[closeIcon accessibilityIdentifier]]; |
| 430 [self configureButton:closeButton |
| 431 withIcon:closeButtonIcon |
| 432 action:@selector(dismissQRScannerView:)]; |
| 433 |
| 434 // Constraints for closeButton. |
| 435 [closeButton setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 436 [NSLayoutConstraint activateConstraints:@[ |
| 437 [[closeButton leadingAnchor] constraintEqualToAnchor:[self leadingAnchor] |
| 438 constant:kButtonPadding], |
| 439 [[closeButton bottomAnchor] constraintEqualToAnchor:[self bottomAnchor] |
| 440 constant:-kButtonPadding] |
| 441 ]]; |
| 442 } |
| 443 |
| 444 - (void)addTorchButton { |
| 445 DCHECK(!_torchButton); |
| 446 _torchButton.reset([[MDCFlatButton alloc] initWithFrame:CGRectZero]); |
| 447 [_torchButton setEnabled:NO]; |
| 448 [self configureButton:_torchButton |
| 449 withIcon:[self torchOffIcon] |
| 450 action:@selector(toggleTorch:)]; |
| 451 [_torchButton setAccessibilityIdentifier:@"qr_scanner_torch_button"]; |
| 452 [_torchButton setAccessibilityLabel: |
| 453 l10n_util::GetNSString( |
| 454 IDS_IOS_QR_SCANNER_TORCH_BUTTON_ACCESSIBILITY_LABEL)]; |
| 455 [_torchButton setAccessibilityValue: |
| 456 l10n_util::GetNSString( |
| 457 IDS_IOS_QR_SCANNER_TORCH_OFF_ACCESSIBILITY_VALUE)]; |
| 458 |
| 459 // Constraints for _torchButton. |
| 460 [_torchButton setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 461 [NSLayoutConstraint activateConstraints:@[ |
| 462 [[_torchButton trailingAnchor] constraintEqualToAnchor:[self trailingAnchor] |
| 463 constant:-kButtonPadding], |
| 464 [[_torchButton bottomAnchor] constraintEqualToAnchor:[self bottomAnchor] |
| 465 constant:-kButtonPadding] |
| 466 ]]; |
| 467 } |
| 468 |
| 469 - (void)addViewportCaptionLabel { |
| 470 UILabel* viewportCaption = [[[UILabel alloc] init] autorelease]; |
| 471 NSString* label = l10n_util::GetNSString(IDS_IOS_QR_SCANNER_VIEWPORT_CAPTION); |
| 472 [viewportCaption setText:label]; |
| 473 [viewportCaption setAccessibilityLabel:label]; |
| 474 [viewportCaption setAccessibilityIdentifier:@"qr_scanner_viewport_caption"]; |
| 475 [viewportCaption setTextColor:[UIColor whiteColor]]; |
| 476 [viewportCaption.layer setShadowColor:[UIColor blackColor].CGColor]; |
| 477 [viewportCaption.layer setShadowOffset:CGSizeZero]; |
| 478 [viewportCaption.layer setShadowRadius:kViewportCaptionShadowRadius]; |
| 479 [viewportCaption.layer setShadowOpacity:kViewportCaptionShadowOpacity]; |
| 480 [viewportCaption.layer setMasksToBounds:NO]; |
| 481 [viewportCaption.layer setShouldRasterize:YES]; |
| 482 [self addSubview:viewportCaption]; |
| 483 |
| 484 // Constraints for viewportCaption. |
| 485 [viewportCaption setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 486 [NSLayoutConstraint activateConstraints:@[ |
| 487 [[viewportCaption centerXAnchor] |
| 488 constraintEqualToAnchor:[self centerXAnchor]], |
| 489 [[viewportCaption centerYAnchor] |
| 490 constraintEqualToAnchor:[self centerYAnchor] |
| 491 constant:GetViewportSize() / 2 + kViewportCaptionPadding] |
| 492 ]]; |
| 493 } |
| 494 |
| 495 - (void)setupPreviewView { |
| 496 DCHECK(!_previewView); |
| 497 _previewView.reset([[VideoPreviewView alloc] initWithFrame:self.frame]); |
| 498 [self insertSubview:_previewView atIndex:0]; |
| 499 } |
| 500 |
| 501 - (void)setupPreviewOverlayView { |
| 502 DCHECK(!_previewOverlay); |
| 503 _previewOverlay.reset([[PreviewOverlayView alloc] initWithFrame:CGRectZero]); |
| 504 [self addSubview:_previewOverlay]; |
| 505 |
| 506 // Add a multiplier of sqrt(2) to the width and height constraints to make |
| 507 // sure that the overlay covers the whole screen during rotation. |
| 508 _overlayWidthConstraint.reset( |
| 509 [[NSLayoutConstraint constraintWithItem:_previewOverlay |
| 510 attribute:NSLayoutAttributeWidth |
| 511 relatedBy:NSLayoutRelationGreaterThanOrEqual |
| 512 toItem:self |
| 513 attribute:NSLayoutAttributeWidth |
| 514 multiplier:sqrt(2) |
| 515 constant:0.0] retain]); |
| 516 |
| 517 _overlayHeightConstraint.reset( |
| 518 [[NSLayoutConstraint constraintWithItem:_previewOverlay |
| 519 attribute:NSLayoutAttributeHeight |
| 520 relatedBy:NSLayoutRelationGreaterThanOrEqual |
| 521 toItem:self |
| 522 attribute:NSLayoutAttributeHeight |
| 523 multiplier:sqrt(2) |
| 524 constant:0.0] retain]); |
| 525 |
| 526 _overlaySquareConstraint.reset([[[_previewOverlay heightAnchor] |
| 527 constraintEqualToAnchor:[_previewOverlay widthAnchor]] retain]); |
| 528 |
| 529 // Constrains the preview overlay to be square, centered, with both width and |
| 530 // height greater than or equal to the width and height of the QRScannerView. |
| 531 [_previewOverlay setTranslatesAutoresizingMaskIntoConstraints:NO]; |
| 532 [NSLayoutConstraint activateConstraints:@[ |
| 533 [[_previewOverlay centerXAnchor] |
| 534 constraintEqualToAnchor:[self centerXAnchor]], |
| 535 [[_previewOverlay centerYAnchor] |
| 536 constraintEqualToAnchor:[self centerYAnchor]], |
| 537 _overlaySquareConstraint, _overlayWidthConstraint, _overlayHeightConstraint |
| 538 ]]; |
| 539 } |
| 540 |
| 541 @end |
OLD | NEW |