diff --git a/WeScan.xcodeproj/project.pbxproj b/WeScan.xcodeproj/project.pbxproj index 5f6f75a..254cc05 100644 --- a/WeScan.xcodeproj/project.pbxproj +++ b/WeScan.xcodeproj/project.pbxproj @@ -15,6 +15,9 @@ 74F7D03A211ACC4B0046AF7E /* UIImageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74F7D039211ACC4B0046AF7E /* UIImageTests.swift */; }; 74F7D03C211ACC6B0046AF7E /* CGAffineTransformTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74F7D03B211ACC6B0046AF7E /* CGAffineTransformTests.swift */; }; 74F7D03E211ACC890046AF7E /* AVCaptureVideoOrientationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74F7D03D211ACC890046AF7E /* AVCaptureVideoOrientationTests.swift */; }; + 9308ED6323434CB800BE8F37 /* UIInterfaceOrientation+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9308ED6223434CB800BE8F37 /* UIInterfaceOrientation+Utils.swift */; }; + 9308ED6423434CB800BE8F37 /* UIInterfaceOrientation+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9308ED6223434CB800BE8F37 /* UIInterfaceOrientation+Utils.swift */; }; + 9308ED6523434CB800BE8F37 /* UIInterfaceOrientation+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9308ED6223434CB800BE8F37 /* UIInterfaceOrientation+Utils.swift */; }; A11C5B9C2046A20C005075FE /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11C5B9B2046A20C005075FE /* Error.swift */; }; A11C5CD920495EA1005075FE /* RectangleFeaturesFunnelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11C5CD820495EA1005075FE /* RectangleFeaturesFunnelTests.swift */; }; A11C5CDB20495EC9005075FE /* AVCaptureVideoOrientation+Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11C5CDA20495EC9005075FE /* AVCaptureVideoOrientation+Utils.swift */; }; @@ -195,6 +198,7 @@ 74F7D039211ACC4B0046AF7E /* UIImageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageTests.swift; sourceTree = ""; }; 74F7D03B211ACC6B0046AF7E /* CGAffineTransformTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGAffineTransformTests.swift; sourceTree = ""; }; 74F7D03D211ACC890046AF7E /* AVCaptureVideoOrientationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVCaptureVideoOrientationTests.swift; sourceTree = ""; }; + 9308ED6223434CB800BE8F37 /* UIInterfaceOrientation+Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIInterfaceOrientation+Utils.swift"; sourceTree = ""; }; 9541C84122155DD3005FBCD3 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; A11C5B9B2046A20C005075FE /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; A11C5CD820495EA1005075FE /* RectangleFeaturesFunnelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RectangleFeaturesFunnelTests.swift; sourceTree = ""; }; @@ -440,6 +444,7 @@ C3E2EB8D20B8970800A42E58 /* UIImage+Utils.swift */, A1DF90F52037187500841A11 /* UIImage+Orientation.swift */, 362967772294C23700B9FC4A /* CGImagePropertyOrientation.swift */, + 9308ED6223434CB800BE8F37 /* UIInterfaceOrientation+Utils.swift */, ); path = Extensions; sourceTree = ""; @@ -792,6 +797,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9308ED6323434CB800BE8F37 /* UIInterfaceOrientation+Utils.swift in Sources */, A1D4BCBD202C4F3800FCDDEC /* HomeViewController.swift in Sources */, A1D4BCBB202C4F3800FCDDEC /* AppDelegate.swift in Sources */, ); @@ -805,6 +811,7 @@ A1DF90F22035992A00841A11 /* CGAffineTransform+Utils.swift in Sources */, A1F22EA3202DAA74001723AD /* RectangleFeaturesFunnel.swift in Sources */, A1D4BD0E202C57A400FCDDEC /* CaptureSessionManager.swift in Sources */, + 9308ED6423434CB800BE8F37 /* UIInterfaceOrientation+Utils.swift in Sources */, B9AAE88B219E6C0400205620 /* FocusRectangleView.swift in Sources */, A1F22ECE2031937E001723AD /* EditScanViewController.swift in Sources */, A1F22E9F202C8D70001723AD /* Quadrilateral.swift in Sources */, @@ -841,6 +848,7 @@ files = ( C3789C9B20CC69AD001B423F /* QuadrilateralViewTests.swift in Sources */, 74F7D038211ACBF90046AF7E /* VisionRectangleDetectorTests.swift in Sources */, + 9308ED6523434CB800BE8F37 /* UIInterfaceOrientation+Utils.swift in Sources */, A1DF90FD203B412600841A11 /* CGPointTests.swift in Sources */, 74F7D03E211ACC890046AF7E /* AVCaptureVideoOrientationTests.swift in Sources */, B94E76AC221AB7D100C1945D /* CGSizeTests.swift in Sources */, @@ -1037,14 +1045,14 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 7P4LQNMZS5; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WeScanSampleProject/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.WeTransfer.WeScanSampleProject; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -1054,14 +1062,14 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = 7P4LQNMZS5; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WeScanSampleProject/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.WeTransfer.WeScanSampleProject; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.2; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; diff --git a/WeScan/Common/Quadrilateral.swift b/WeScan/Common/Quadrilateral.swift index 6269e20..18b28cc 100644 --- a/WeScan/Common/Quadrilateral.swift +++ b/WeScan/Common/Quadrilateral.swift @@ -147,7 +147,7 @@ public struct Quadrilateral: Transformable { var invertedfromSize = fromSize let rotated = rotationAngle != 0.0 - if rotated && rotationAngle != CGFloat.pi { + if rotated && (rotationAngle.truncatingRemainder(dividingBy: CGFloat.pi) != 0) { invertedfromSize = CGSize(width: fromSize.height, height: fromSize.width) } diff --git a/WeScan/Extensions/AVCaptureVideoOrientation+Utils.swift b/WeScan/Extensions/AVCaptureVideoOrientation+Utils.swift index 5126d99..b548d21 100644 --- a/WeScan/Extensions/AVCaptureVideoOrientation+Utils.swift +++ b/WeScan/Extensions/AVCaptureVideoOrientation+Utils.swift @@ -14,21 +14,24 @@ extension AVCaptureVideoOrientation { /// Maps UIDeviceOrientation to AVCaptureVideoOrientation init?(deviceOrientation: UIDeviceOrientation) { switch deviceOrientation { - case .portrait: - self.init(rawValue: AVCaptureVideoOrientation.portrait.rawValue) - case .portraitUpsideDown: - self.init(rawValue: AVCaptureVideoOrientation.portraitUpsideDown.rawValue) - case .landscapeLeft: - self.init(rawValue: AVCaptureVideoOrientation.landscapeLeft.rawValue) - case .landscapeRight: - self.init(rawValue: AVCaptureVideoOrientation.landscapeRight.rawValue) - case .faceUp: - self.init(rawValue: AVCaptureVideoOrientation.portrait.rawValue) - case .faceDown: - self.init(rawValue: AVCaptureVideoOrientation.portraitUpsideDown.rawValue) - default: - self.init(rawValue: AVCaptureVideoOrientation.portrait.rawValue) + case .portrait: self = .portrait + case .portraitUpsideDown: self = .portraitUpsideDown + case .landscapeLeft: self = .landscapeLeft + case .landscapeRight: self = .landscapeRight + case .faceUp: self = .portrait + case .faceDown: self = .portraitUpsideDown + default: self = .portrait + } + } + + /// Maps UIInterfaceOrientation to AVCaptureVideoOrientation + init?(interfaceOrientation: UIInterfaceOrientation) { + switch interfaceOrientation { + case .portrait: self = .portrait + case .portraitUpsideDown: self = .portraitUpsideDown + case .landscapeLeft: self = .landscapeLeft + case .landscapeRight: self = .landscapeRight + default: return nil } } - } diff --git a/WeScan/Extensions/CGImagePropertyOrientation.swift b/WeScan/Extensions/CGImagePropertyOrientation.swift index 7d86eea..e9edeed 100644 --- a/WeScan/Extensions/CGImagePropertyOrientation.swift +++ b/WeScan/Extensions/CGImagePropertyOrientation.swift @@ -31,4 +31,32 @@ extension CGImagePropertyOrientation { self = .right } } + + init(deviceOrientation: UIDeviceOrientation) { + switch deviceOrientation { + case .portrait: self = .up + case .portraitUpsideDown: self = .down + case .landscapeLeft: self = .left + case .landscapeRight: self = .right + case .unknown: self = .up + case .faceUp: self = .up + case .faceDown: self = .up + @unknown default: + assertionFailure("Unknow orientation, falling to default") + self = .right + } + } + + init(interfaceOrientation: UIInterfaceOrientation) { + switch interfaceOrientation { + case .portrait: self = .up + case .portraitUpsideDown: self = .up + case .landscapeLeft: self = .left + case .landscapeRight: self = .right + case .unknown: self = .up + @unknown default: + assertionFailure("Unknow orientation, falling to default") + self = .right + } + } } diff --git a/WeScan/Extensions/UIInterfaceOrientation+Utils.swift b/WeScan/Extensions/UIInterfaceOrientation+Utils.swift new file mode 100644 index 0000000..7d742d1 --- /dev/null +++ b/WeScan/Extensions/UIInterfaceOrientation+Utils.swift @@ -0,0 +1,27 @@ +// +// UIInterfaceOrientation+Utils.swift +// WeScan +// +// Created by Antoine Harlin on 01/10/2019. +// Copyright © 2019 WeTransfer. All rights reserved. +// + +import Foundation +import UIKit + +extension UIInterfaceOrientation { + var rotationAngle: CGFloat { + switch self { + case UIInterfaceOrientation.portrait: + return CGFloat.pi / 2 + case UIInterfaceOrientation.portraitUpsideDown: + return -CGFloat.pi / 2 + case UIInterfaceOrientation.landscapeLeft: + return CGFloat.pi + case UIInterfaceOrientation.landscapeRight: + return CGFloat.pi * 2 + default: + return CGFloat.pi / 2 + } + } +} diff --git a/WeScan/Scan/CaptureSessionManager.swift b/WeScan/Scan/CaptureSessionManager.swift index e087980..9dc0552 100644 --- a/WeScan/Scan/CaptureSessionManager.swift +++ b/WeScan/Scan/CaptureSessionManager.swift @@ -280,6 +280,7 @@ extension CaptureSessionManager: AVCapturePhotoCaptureDelegate { /// Completes the image capture by processing the image, and passing it to the delegate object. /// This function is necessary because the capture functions for iOS 10 and 11 are decoupled. private func completeImageCapture(with imageData: Data) { + let statusBarOrientation = UIApplication.shared.statusBarOrientation DispatchQueue.global(qos: .background).async { [weak self] in CaptureSession.current.isEditing = true guard let image = UIImage(data: imageData) else { @@ -292,29 +293,25 @@ extension CaptureSessionManager: AVCapturePhotoCaptureDelegate { } return } - - var angle: CGFloat = 0.0 - - switch image.imageOrientation { - case .right: - angle = CGFloat.pi / 2 - case .up: - angle = CGFloat.pi - default: - break - } - + + let floatAngle = statusBarOrientation.rotationAngle + CGFloat.pi + let angle = Measurement(value: Double(floatAngle), unit: UnitAngle.radians) + let orientedImage = image.rotated(by: angle) ?? image var quad: Quadrilateral? if let displayedRectangleResult = self?.displayedRectangleResult { quad = self?.displayRectangleResult(rectangleResult: displayedRectangleResult) - quad = quad?.scale(displayedRectangleResult.imageSize, image.size, withRotationAngle: angle) + quad = quad?.scale( + displayedRectangleResult.imageSize, + orientedImage.size, + withRotationAngle: statusBarOrientation.rotationAngle + ) } - + DispatchQueue.main.async { guard let strongSelf = self else { return } - strongSelf.delegate?.captureSessionManager(strongSelf, didCapturePicture: image, withQuad: quad) + strongSelf.delegate?.captureSessionManager(strongSelf, didCapturePicture: orientedImage, withQuad: quad) } } } diff --git a/WeScan/Scan/ScannerViewController.swift b/WeScan/Scan/ScannerViewController.swift index c53aea5..d61a079 100644 --- a/WeScan/Scan/ScannerViewController.swift +++ b/WeScan/Scan/ScannerViewController.swift @@ -102,17 +102,23 @@ final class ScannerViewController: UIViewController { navigationController?.navigationBar.sendSubviewToBack(visualEffectView) navigationController?.navigationBar.barStyle = .blackTranslucent + + setupVideoOrientation() + setupViewsForCurrentOrientation() } - - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - - videoPreviewLayer.frame = view.layer.bounds - - let statusBarHeight = UIApplication.shared.statusBarFrame.size.height - let visualEffectRect = self.navigationController?.navigationBar.bounds.insetBy(dx: 0, dy: -(statusBarHeight)).offsetBy(dx: 0, dy: -statusBarHeight) - - visualEffectView.frame = visualEffectRect ?? CGRect.zero + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + setupViewsForCurrentOrientation() + } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + coordinator.animate(alongsideTransition: { [weak self] (context) in + self?.setupViewsForCurrentOrientation() + }, completion: { [weak self] (_) in + self?.setupVideoOrientation() + }) } override func viewWillDisappear(_ animated: Bool) { @@ -140,7 +146,20 @@ final class ScannerViewController: UIViewController { view.addSubview(shutterButton) view.addSubview(activityIndicator) } - + + private func setupViewsForCurrentOrientation() { + let statusBarHeight = UIApplication.shared.statusBarFrame.size.height + let visualEffectRect = self.navigationController?.navigationBar.bounds.insetBy( + dx: 0, + dy: -(statusBarHeight) + ).offsetBy( + dx: 0, + dy: -statusBarHeight + ) + self.visualEffectView.frame = visualEffectRect ?? CGRect.zero + videoPreviewLayer.frame = view.layer.bounds + } + private func setupNavigationBar() { navigationItem.setLeftBarButton(flashButton, animated: false) navigationItem.setRightBarButton(autoScanButton, animated: false) @@ -196,6 +215,17 @@ final class ScannerViewController: UIViewController { NSLayoutConstraint.activate(quadViewConstraints + cancelButtonConstraints + shutterButtonConstraints + activityIndicatorConstraints) } + + func setupVideoOrientation() { + let statusBarOrientation = UIApplication.shared.statusBarOrientation + var initialVideoOrientation: AVCaptureVideoOrientation = .portrait + if statusBarOrientation != .unknown { + if let videoOrientation = AVCaptureVideoOrientation(rawValue: statusBarOrientation.rawValue) { + initialVideoOrientation = videoOrientation + } + } + videoPreviewLayer.connection?.videoOrientation = initialVideoOrientation + } // MARK: - Tap to Focus @@ -302,7 +332,6 @@ extension ScannerViewController: RectangleDetectionDelegateProtocol { func captureSessionManager(_ captureSessionManager: CaptureSessionManager, didCapturePicture picture: UIImage, withQuad quad: Quadrilateral?) { activityIndicator.stopAnimating() - let editVC = EditScanViewController(image: picture, quad: quad) navigationController?.pushViewController(editVC, animated: false) @@ -315,13 +344,18 @@ extension ScannerViewController: RectangleDetectionDelegateProtocol { quadView.removeQuadrilateral() return } + + let statusBarOrientation = UIApplication.shared.statusBarOrientation + + let orientedImageSize = CGSize( + width: statusBarOrientation.isPortrait ? imageSize.height : imageSize.width, + height: statusBarOrientation.isPortrait ? imageSize.width : imageSize.height + ) - let portraitImageSize = CGSize(width: imageSize.height, height: imageSize.width) - - let scaleTransform = CGAffineTransform.scaleTransform(forSize: portraitImageSize, aspectFillInSize: quadView.bounds.size) + let scaleTransform = CGAffineTransform.scaleTransform(forSize: orientedImageSize, aspectFillInSize: quadView.bounds.size) let scaledImageSize = imageSize.applying(scaleTransform) - let rotationTransform = CGAffineTransform(rotationAngle: CGFloat.pi / 2.0) + let rotationTransform = CGAffineTransform(rotationAngle: statusBarOrientation.rotationAngle) let imageBounds = CGRect(origin: .zero, size: scaledImageSize).applying(rotationTransform) diff --git a/WeScanSampleProject/HomeViewController.swift b/WeScanSampleProject/HomeViewController.swift index af066e5..12abee6 100644 --- a/WeScanSampleProject/HomeViewController.swift +++ b/WeScanSampleProject/HomeViewController.swift @@ -96,7 +96,7 @@ final class HomeViewController: UIViewController { @objc func scanOrSelectImage(_ sender: UIButton) { let actionSheet = UIAlertController(title: "Would you like to scan an image or select one from your photo library?", message: nil, preferredStyle: .actionSheet) - + actionSheet.popoverPresentationController?.sourceView = sender let scanAction = UIAlertAction(title: "Scan", style: .default) { (_) in self.scanImage() } @@ -116,6 +116,7 @@ final class HomeViewController: UIViewController { func scanImage() { let scannerViewController = ImageScannerController(delegate: self) + scannerViewController.modalPresentationStyle = .fullScreen present(scannerViewController, animated: true) } @@ -153,6 +154,7 @@ extension HomeViewController: UIImagePickerControllerDelegate, UINavigationContr guard let image = info[.originalImage] as? UIImage else { return } let scannerViewController = ImageScannerController(image: image, delegate: self) + scannerViewController.modalPresentationStyle = .fullScreen present(scannerViewController, animated: true) } }