ScannerViewController.swift 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. //
  2. // ScannerViewController.swift
  3. // WeScan
  4. //
  5. // Created by Boris Emorine on 2/8/18.
  6. // Copyright © 2018 WeTransfer. All rights reserved.
  7. //
  8. import UIKit
  9. import AVFoundation
  10. @available(iOS 10, *)
  11. /// The `ScannerViewController` offers an interface to give feedback to the user regarding quadrilaterals that are detected. It also gives the user the opportunity to capture an image with a detected rectangle.
  12. final class ScannerViewController: UIViewController {
  13. private var captureSessionManager: CaptureSessionManager?
  14. private let videoPreviewlayer = AVCaptureVideoPreviewLayer()
  15. /// The view that draws the detected rectangles.
  16. private let quadView = QuadrilateralView()
  17. lazy private var shutterButton: ShutterButton = {
  18. let button = ShutterButton()
  19. button.translatesAutoresizingMaskIntoConstraints = false
  20. button.addTarget(self, action: #selector(captureImage(_:)), for: .touchUpInside)
  21. return button
  22. }()
  23. lazy private var activityIndicator: UIActivityIndicatorView = {
  24. let activityIndicator = UIActivityIndicatorView(style: .gray)
  25. activityIndicator.hidesWhenStopped = true
  26. activityIndicator.translatesAutoresizingMaskIntoConstraints = false
  27. return activityIndicator
  28. }()
  29. lazy private var closeButton: CloseButton = {
  30. let button = CloseButton(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
  31. button.addTarget(self, action: #selector(cancelImageScannerController(_:)), for: .touchUpInside)
  32. return button
  33. }()
  34. // MARK: - Life Cycle
  35. override func viewDidLoad() {
  36. super.viewDidLoad()
  37. title = NSLocalizedString("wescan.scanning.title", comment: "The title of the ScannerViewController")
  38. navigationItem.leftBarButtonItem = UIBarButtonItem(customView: closeButton)
  39. setupViews()
  40. setupConstraints()
  41. captureSessionManager = CaptureSessionManager(videoPreviewLayer: videoPreviewlayer)
  42. captureSessionManager?.delegate = self
  43. }
  44. override func viewWillAppear(_ animated: Bool) {
  45. super.viewWillAppear(animated)
  46. quadView.removeQuadrilateral()
  47. captureSessionManager?.start()
  48. UIApplication.shared.isIdleTimerDisabled = true
  49. self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: NCBrandColor.sharedInstance.brandText]
  50. self.navigationController?.navigationBar.barTintColor = NCBrandColor.sharedInstance.brand
  51. }
  52. override func viewDidLayoutSubviews() {
  53. super.viewDidLayoutSubviews()
  54. videoPreviewlayer.frame = view.layer.bounds
  55. }
  56. override func viewWillDisappear(_ animated: Bool) {
  57. super.viewWillDisappear(animated)
  58. UIApplication.shared.isIdleTimerDisabled = false
  59. }
  60. // MARK: - Setups
  61. private func setupViews() {
  62. view.layer.addSublayer(videoPreviewlayer)
  63. quadView.translatesAutoresizingMaskIntoConstraints = false
  64. quadView.editable = false
  65. view.addSubview(quadView)
  66. view.addSubview(shutterButton)
  67. view.addSubview(activityIndicator)
  68. }
  69. private func setupConstraints() {
  70. let quadViewConstraints = [
  71. quadView.topAnchor.constraint(equalTo: view.topAnchor),
  72. view.bottomAnchor.constraint(equalTo: quadView.bottomAnchor),
  73. view.trailingAnchor.constraint(equalTo: quadView.trailingAnchor),
  74. quadView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
  75. ]
  76. var shutterButtonBottomConstraint: NSLayoutConstraint
  77. if #available(iOS 11.0, *) {
  78. shutterButtonBottomConstraint = view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: shutterButton.bottomAnchor, constant: 15.0)
  79. } else {
  80. shutterButtonBottomConstraint = view.bottomAnchor.constraint(equalTo: shutterButton.bottomAnchor, constant: 15.0)
  81. }
  82. let shutterButtonConstraints = [
  83. shutterButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  84. shutterButtonBottomConstraint,
  85. shutterButton.widthAnchor.constraint(equalToConstant: 65.0),
  86. shutterButton.heightAnchor.constraint(equalToConstant: 65.0)
  87. ]
  88. let activityIndicatorConstraints = [
  89. activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  90. activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
  91. ]
  92. NSLayoutConstraint.activate(quadViewConstraints + shutterButtonConstraints + activityIndicatorConstraints)
  93. }
  94. // MARK: - Actions
  95. @objc private func captureImage(_ sender: UIButton) {
  96. (navigationController as? ImageScannerController)?.flashToBlack()
  97. shutterButton.isUserInteractionEnabled = false
  98. captureSessionManager?.capturePhoto()
  99. }
  100. @objc private func cancelImageScannerController(_ sender: UIButton) {
  101. if let imageScannerController = navigationController as? ImageScannerController {
  102. imageScannerController.imageScannerDelegate?.imageScannerControllerDidCancel(imageScannerController)
  103. }
  104. }
  105. }
  106. @available(iOS 10, *)
  107. extension ScannerViewController: RectangleDetectionDelegateProtocol {
  108. func captureSessionManager(_ captureSessionManager: CaptureSessionManager, didFailWithError error: Error) {
  109. activityIndicator.stopAnimating()
  110. shutterButton.isUserInteractionEnabled = true
  111. if let imageScannerController = navigationController as? ImageScannerController {
  112. imageScannerController.imageScannerDelegate?.imageScannerController(imageScannerController, didFailWithError: error)
  113. }
  114. }
  115. func didStartCapturingPicture(for captureSessionManager: CaptureSessionManager) {
  116. activityIndicator.startAnimating()
  117. shutterButton.isUserInteractionEnabled = false
  118. }
  119. func captureSessionManager(_ captureSessionManager: CaptureSessionManager, didCapturePicture picture: UIImage, withQuad quad: Quadrilateral?) {
  120. activityIndicator.stopAnimating()
  121. let editVC = EditScanViewController(image: picture, quad: quad)
  122. navigationController?.pushViewController(editVC, animated: false)
  123. shutterButton.isUserInteractionEnabled = true
  124. }
  125. func captureSessionManager(_ captureSessionManager: CaptureSessionManager, didDetectQuad quad: Quadrilateral?, _ imageSize: CGSize) {
  126. guard let quad = quad else {
  127. // If no quad has been detected, we remove the currently displayed on on the quadView.
  128. quadView.removeQuadrilateral()
  129. return
  130. }
  131. let portraitImageSize = CGSize(width: imageSize.height, height: imageSize.width)
  132. let scaleTransform = CGAffineTransform.scaleTransform(forSize: portraitImageSize, aspectFillInSize: quadView.bounds.size)
  133. let scaledImageSize = imageSize.applying(scaleTransform)
  134. let rotationTransform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2.0))
  135. let imageBounds = CGRect(x: 0.0, y: 0.0, width: scaledImageSize.width, height: scaledImageSize.height).applying(rotationTransform)
  136. let translationTransform = CGAffineTransform.translateTransform(fromCenterOfRect: imageBounds, toCenterOfRect: quadView.bounds)
  137. let transforms = [scaleTransform, rotationTransform, translationTransform]
  138. let transformedQuad = quad.applyTransforms(transforms)
  139. quadView.drawQuadrilateral(quad: transformedQuad, animated: true)
  140. }
  141. }