ScannerViewController.swift 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  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(activityIndicatorStyle: .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: 18, height: 18))
  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. }
  50. override func viewDidLayoutSubviews() {
  51. super.viewDidLayoutSubviews()
  52. videoPreviewlayer.frame = view.layer.bounds
  53. }
  54. override func viewWillDisappear(_ animated: Bool) {
  55. super.viewWillDisappear(animated)
  56. UIApplication.shared.isIdleTimerDisabled = false
  57. }
  58. // MARK: - Setups
  59. private func setupViews() {
  60. view.layer.addSublayer(videoPreviewlayer)
  61. quadView.translatesAutoresizingMaskIntoConstraints = false
  62. quadView.editable = false
  63. view.addSubview(quadView)
  64. view.addSubview(shutterButton)
  65. view.addSubview(activityIndicator)
  66. }
  67. private func setupConstraints() {
  68. let quadViewConstraints = [
  69. quadView.topAnchor.constraint(equalTo: view.topAnchor),
  70. view.bottomAnchor.constraint(equalTo: quadView.bottomAnchor),
  71. view.trailingAnchor.constraint(equalTo: quadView.trailingAnchor),
  72. quadView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
  73. ]
  74. var shutterButtonBottomConstraint: NSLayoutConstraint
  75. if #available(iOS 11.0, *) {
  76. shutterButtonBottomConstraint = view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: shutterButton.bottomAnchor, constant: 15.0)
  77. } else {
  78. shutterButtonBottomConstraint = view.bottomAnchor.constraint(equalTo: shutterButton.bottomAnchor, constant: 15.0)
  79. }
  80. let shutterButtonConstraints = [
  81. shutterButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  82. shutterButtonBottomConstraint,
  83. shutterButton.widthAnchor.constraint(equalToConstant: 65.0),
  84. shutterButton.heightAnchor.constraint(equalToConstant: 65.0)
  85. ]
  86. let activityIndicatorConstraints = [
  87. activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
  88. activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
  89. ]
  90. NSLayoutConstraint.activate(quadViewConstraints + shutterButtonConstraints + activityIndicatorConstraints)
  91. }
  92. // MARK: - Actions
  93. @objc private func captureImage(_ sender: UIButton) {
  94. (navigationController as? ImageScannerController)?.flashToBlack()
  95. shutterButton.isUserInteractionEnabled = false
  96. captureSessionManager?.capturePhoto()
  97. }
  98. @objc private func cancelImageScannerController(_ sender: UIButton) {
  99. if let imageScannerController = navigationController as? ImageScannerController {
  100. imageScannerController.imageScannerDelegate?.imageScannerControllerDidCancel(imageScannerController)
  101. }
  102. }
  103. }
  104. @available(iOS 10, *)
  105. extension ScannerViewController: RectangleDetectionDelegateProtocol {
  106. func captureSessionManager(_ captureSessionManager: CaptureSessionManager, didFailWithError error: Error) {
  107. activityIndicator.stopAnimating()
  108. shutterButton.isUserInteractionEnabled = true
  109. if let imageScannerController = navigationController as? ImageScannerController {
  110. imageScannerController.imageScannerDelegate?.imageScannerController(imageScannerController, didFailWithError: error)
  111. }
  112. }
  113. func didStartCapturingPicture(for captureSessionManager: CaptureSessionManager) {
  114. activityIndicator.startAnimating()
  115. shutterButton.isUserInteractionEnabled = false
  116. }
  117. func captureSessionManager(_ captureSessionManager: CaptureSessionManager, didCapturePicture picture: UIImage, withQuad quad: Quadrilateral?) {
  118. activityIndicator.stopAnimating()
  119. let editVC = EditScanViewController(image: picture, quad: quad)
  120. navigationController?.pushViewController(editVC, animated: false)
  121. shutterButton.isUserInteractionEnabled = true
  122. }
  123. func captureSessionManager(_ captureSessionManager: CaptureSessionManager, didDetectQuad quad: Quadrilateral?, _ imageSize: CGSize) {
  124. guard let quad = quad else {
  125. // If no quad has been detected, we remove the currently displayed on on the quadView.
  126. quadView.removeQuadrilateral()
  127. return
  128. }
  129. let portraitImageSize = CGSize(width: imageSize.height, height: imageSize.width)
  130. let scaleTransform = CGAffineTransform.scaleTransform(forSize: portraitImageSize, aspectFillInSize: quadView.bounds.size)
  131. let scaledImageSize = imageSize.applying(scaleTransform)
  132. let rotationTransform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2.0))
  133. let imageBounds = CGRect(x: 0.0, y: 0.0, width: scaledImageSize.width, height: scaledImageSize.height).applying(rotationTransform)
  134. let translationTransform = CGAffineTransform.translateTransform(fromCenterOfRect: imageBounds, toCenterOfRect: quadView.bounds)
  135. let transforms = [scaleTransform, rotationTransform, translationTransform]
  136. let transformedQuad = quad.applyTransforms(transforms)
  137. quadView.drawQuadrilateral(quad: transformedQuad, animated: true)
  138. }
  139. }