Skip to content

Commit

Permalink
Allow support for using an image after instantiation (#251)
Browse files Browse the repository at this point in the history
* Allow support for using an image after instantiation

* Make ScannerViewController public so it can be accessed

* SwiftLint fixes

* Code review feedback

Co-authored-by: Erik Villegas <erik.villegas@carvana.com>
  • Loading branch information
erikvillegas and Erik Villegas authored Jul 7, 2020
1 parent e7b1755 commit 4b94435
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 28 deletions.
61 changes: 39 additions & 22 deletions WeScan/ImageScannerController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,29 +76,10 @@ public final class ImageScannerController: UINavigationController {

// If an image was passed in by the host app (e.g. picked from the photo library), use it instead of the document scanner.
if let image = image {

var detectedQuad: Quadrilateral?

// Whether or not we detect a quad, present the edit view controller after attempting to detect a quad.
// *** Vision *requires* a completion block to detect rectangles, but it's instant.
// *** When using Vision, we'll present the normal edit view controller first, then present the updated edit view controller later.

guard let ciImage = CIImage(image: image) else { return }
let orientation = CGImagePropertyOrientation(image.imageOrientation)
let orientedImage = ciImage.oriented(forExifOrientation: Int32(orientation.rawValue))
if #available(iOS 11.0, *) {

// Use the VisionRectangleDetector on iOS 11 to attempt to find a rectangle from the initial image.
VisionRectangleDetector.rectangle(forImage: ciImage, orientation: orientation) { (quad) in
detectedQuad = quad?.toCartesian(withHeight: orientedImage.extent.height)
let editViewController = EditScanViewController(image: image, quad: detectedQuad, rotateImage: false)
self.setViewControllers([editViewController], animated: true)
}
} else {
// Use the CIRectangleDetector on iOS 10 to attempt to find a rectangle from the initial image.
detectedQuad = CIRectangleDetector.rectangle(forImage: ciImage)?.toCartesian(withHeight: orientedImage.extent.height)
detect(image: image) { [weak self] detectedQuad in
guard let self = self else { return }
let editViewController = EditScanViewController(image: image, quad: detectedQuad, rotateImage: false)
setViewControllers([editViewController], animated: false)
self.setViewControllers([editViewController], animated: false)
}
}
}
Expand All @@ -111,6 +92,42 @@ public final class ImageScannerController: UINavigationController {
fatalError("init(coder:) has not been implemented")
}

private func detect(image: UIImage, completion: @escaping (Quadrilateral?) -> Void) {
// Whether or not we detect a quad, present the edit view controller after attempting to detect a quad.
// *** Vision *requires* a completion block to detect rectangles, but it's instant.
// *** When using Vision, we'll present the normal edit view controller first, then present the updated edit view controller later.

guard let ciImage = CIImage(image: image) else { return }
let orientation = CGImagePropertyOrientation(image.imageOrientation)
let orientedImage = ciImage.oriented(forExifOrientation: Int32(orientation.rawValue))

if #available(iOS 11.0, *) {
// Use the VisionRectangleDetector on iOS 11 to attempt to find a rectangle from the initial image.
VisionRectangleDetector.rectangle(forImage: ciImage, orientation: orientation) { (quad) in
let detectedQuad = quad?.toCartesian(withHeight: orientedImage.extent.height)
completion(detectedQuad)
}
} else {
// Use the CIRectangleDetector on iOS 10 to attempt to find a rectangle from the initial image.
let detectedQuad = CIRectangleDetector.rectangle(forImage: ciImage)?.toCartesian(withHeight: orientedImage.extent.height)
completion(detectedQuad)
}
}

public func useImage(image: UIImage) {
guard topViewController is ScannerViewController else { return }

detect(image: image) { [weak self] detectedQuad in
guard let self = self else { return }
let editViewController = EditScanViewController(image: image, quad: detectedQuad, rotateImage: false)
self.setViewControllers([editViewController], animated: true)
}
}

public func resetScanner() {
setViewControllers([ScannerViewController()], animated: true)
}

private func setupConstraints() {
let blackFlashViewConstraints = [
blackFlashView.topAnchor.constraint(equalTo: view.topAnchor),
Expand Down
13 changes: 7 additions & 6 deletions WeScan/Scan/ScannerViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import UIKit
import AVFoundation

/// 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.
final class ScannerViewController: UIViewController {
public final class ScannerViewController: UIViewController {

private var captureSessionManager: CaptureSessionManager?
private let videoPreviewLayer = AVCaptureVideoPreviewLayer()
Expand Down Expand Up @@ -67,10 +67,11 @@ final class ScannerViewController: UIViewController {

// MARK: - Life Cycle

override func viewDidLoad() {
override public func viewDidLoad() {
super.viewDidLoad()

title = nil
view.backgroundColor = UIColor.black

setupViews()
setupNavigationBar()
Expand All @@ -83,7 +84,7 @@ final class ScannerViewController: UIViewController {
NotificationCenter.default.addObserver(self, selector: #selector(subjectAreaDidChange), name: Notification.Name.AVCaptureDeviceSubjectAreaDidChange, object: nil)
}

override func viewWillAppear(_ animated: Bool) {
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setNeedsStatusBarAppearanceUpdate()

Expand All @@ -95,13 +96,13 @@ final class ScannerViewController: UIViewController {
navigationController?.navigationBar.barStyle = .blackTranslucent
}

override func viewDidLayoutSubviews() {
override public func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()

videoPreviewLayer.frame = view.layer.bounds
}

override func viewWillDisappear(_ animated: Bool) {
override public func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
UIApplication.shared.isIdleTimerDisabled = false

Expand Down Expand Up @@ -201,7 +202,7 @@ final class ScannerViewController: UIViewController {
CaptureSession.current.removeFocusRectangleIfNeeded(focusRectangle, animated: true)
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)

guard let touch = touches.first else { return }
Expand Down

0 comments on commit 4b94435

Please sign in to comment.