From 04faa034e30a5093cff95e8c4bad4148872c6c8e Mon Sep 17 00:00:00 2001 From: Jon Andersen Date: Thu, 7 Sep 2017 17:00:18 -0400 Subject: [PATCH 1/5] Move orientation logic to separate class --- Source/Orientation.swift | 94 ++++++++++++++++++++++++++++ Source/SwiftyCamViewController.swift | 72 +++------------------ 2 files changed, 101 insertions(+), 65 deletions(-) create mode 100644 Source/Orientation.swift diff --git a/Source/Orientation.swift b/Source/Orientation.swift new file mode 100644 index 0000000..de74816 --- /dev/null +++ b/Source/Orientation.swift @@ -0,0 +1,94 @@ +/*Copyright (c) 2016, Andrew Walz. + + Redistribution and use in source and binary forms, with or without modification,are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +import Foundation +import AVFoundation +import UIKit + +protocol Orientation: class { + func getPreviewLayerOrientation() -> AVCaptureVideoOrientation + func getVideoOrientation() -> AVCaptureVideoOrientation? + func getImageOrientation(forCamera: SwiftyCamViewController.CameraSelection) -> UIImageOrientation + func start() + func stop() +} + +class DeviceOrientation : Orientation { + var deviceOrientation : UIDeviceOrientation? + var shouldUseDeviceOrientation: Bool = false + + func start() { + self.deviceOrientation = UIDevice.current.orientation + NotificationCenter.default.addObserver(self, selector: #selector(didRotate), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) + } + + func stop() { + NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) + self.deviceOrientation = nil + } + + @objc func didRotate() { + if !UIDevice.current.orientation.isFlat { + self.deviceOrientation = UIDevice.current.orientation + } + } + + func getImageOrientation(forCamera: SwiftyCamViewController.CameraSelection) -> UIImageOrientation { + guard shouldUseDeviceOrientation, let deviceOrientation = self.deviceOrientation else { return forCamera == .rear ? .right : .leftMirrored } + + switch deviceOrientation { + case .landscapeLeft: + return forCamera == .rear ? .up : .downMirrored + case .landscapeRight: + return forCamera == .rear ? .down : .upMirrored + case .portraitUpsideDown: + return forCamera == .rear ? .left : .rightMirrored + default: + return forCamera == .rear ? .right : .leftMirrored + } + } + + func getPreviewLayerOrientation() -> AVCaptureVideoOrientation { + // Depends on layout orientation, not device orientation + switch UIApplication.shared.statusBarOrientation { + case .portrait, .unknown: + return AVCaptureVideoOrientation.portrait + case .landscapeLeft: + return AVCaptureVideoOrientation.landscapeLeft + case .landscapeRight: + return AVCaptureVideoOrientation.landscapeRight + case .portraitUpsideDown: + return AVCaptureVideoOrientation.portraitUpsideDown + } + } + + func getVideoOrientation() -> AVCaptureVideoOrientation? { + guard shouldUseDeviceOrientation, let deviceOrientation = self.deviceOrientation else { return nil } + + switch deviceOrientation { + case .landscapeLeft: + return .landscapeRight + case .landscapeRight: + return .landscapeLeft + case .portraitUpsideDown: + return .portraitUpsideDown + default: + return .portrait + } + } +} + + + diff --git a/Source/SwiftyCamViewController.swift b/Source/SwiftyCamViewController.swift index 4a7e743..7a8cc77 100644 --- a/Source/SwiftyCamViewController.swift +++ b/Source/SwiftyCamViewController.swift @@ -233,7 +233,7 @@ open class SwiftyCamViewController: UIViewController { /// PreviewView for the capture session - fileprivate var previewLayer : PreviewView! + var previewLayer : PreviewView! /// UIView for front facing flash @@ -245,7 +245,7 @@ open class SwiftyCamViewController: UIViewController { /// Last changed orientation - fileprivate var deviceOrientation : UIDeviceOrientation? + var orientation : Orientation = DeviceOrientation() /// Boolean to store when View Controller is notified session is running @@ -368,7 +368,7 @@ open class SwiftyCamViewController: UIViewController { // Subscribe to device rotation notifications if shouldUseDeviceOrientation { - subscribeToDeviceOrientationChangeNotifications() + orientation.start() } // Set background audio preference @@ -384,7 +384,7 @@ open class SwiftyCamViewController: UIViewController { // Preview layer video orientation can be set only after the connection is created DispatchQueue.main.async { - self.previewLayer.videoPreviewLayer.connection?.videoOrientation = self.getPreviewLayerOrientation() + self.previewLayer.videoPreviewLayer.connection?.videoOrientation = self.orientation.getPreviewLayerOrientation() } case .notAuthorized: @@ -424,7 +424,7 @@ open class SwiftyCamViewController: UIViewController { // Unsubscribe from device rotation notifications if shouldUseDeviceOrientation { - unsubscribeFromDeviceOrientationChangeNotifications() + orientation.stop() } } @@ -519,7 +519,7 @@ open class SwiftyCamViewController: UIViewController { movieFileOutputConnection?.isVideoMirrored = true } - movieFileOutputConnection?.videoOrientation = self.getVideoOrientation() + movieFileOutputConnection?.videoOrientation = self.orientation.getVideoOrientation() ?? self.previewLayer.videoPreviewLayer.connection!.videoOrientation // Start recording to a temporary file. let outputFileName = UUID().uuidString @@ -776,65 +776,7 @@ open class SwiftyCamViewController: UIViewController { /// Orientation management - fileprivate func subscribeToDeviceOrientationChangeNotifications() { - self.deviceOrientation = UIDevice.current.orientation - NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) - } - fileprivate func unsubscribeFromDeviceOrientationChangeNotifications() { - NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) - self.deviceOrientation = nil - } - - @objc fileprivate func deviceDidRotate() { - if !UIDevice.current.orientation.isFlat { - self.deviceOrientation = UIDevice.current.orientation - } - } - - fileprivate func getPreviewLayerOrientation() -> AVCaptureVideoOrientation { - // Depends on layout orientation, not device orientation - switch UIApplication.shared.statusBarOrientation { - case .portrait, .unknown: - return AVCaptureVideoOrientation.portrait - case .landscapeLeft: - return AVCaptureVideoOrientation.landscapeLeft - case .landscapeRight: - return AVCaptureVideoOrientation.landscapeRight - case .portraitUpsideDown: - return AVCaptureVideoOrientation.portraitUpsideDown - } - } - - fileprivate func getVideoOrientation() -> AVCaptureVideoOrientation { - guard shouldUseDeviceOrientation, let deviceOrientation = self.deviceOrientation else { return previewLayer!.videoPreviewLayer.connection!.videoOrientation } - - switch deviceOrientation { - case .landscapeLeft: - return .landscapeRight - case .landscapeRight: - return .landscapeLeft - case .portraitUpsideDown: - return .portraitUpsideDown - default: - return .portrait - } - } - - fileprivate func getImageOrientation(forCamera: CameraSelection) -> UIImageOrientation { - guard shouldUseDeviceOrientation, let deviceOrientation = self.deviceOrientation else { return forCamera == .rear ? .right : .leftMirrored } - - switch deviceOrientation { - case .landscapeLeft: - return forCamera == .rear ? .up : .downMirrored - case .landscapeRight: - return forCamera == .rear ? .down : .upMirrored - case .portraitUpsideDown: - return forCamera == .rear ? .left : .rightMirrored - default: - return forCamera == .rear ? .right : .leftMirrored - } - } /** Returns a UIImage from Image Data. @@ -851,7 +793,7 @@ open class SwiftyCamViewController: UIViewController { // Set proper orientation for photo // If camera is currently set to front camera, flip image - let image = UIImage(cgImage: cgImageRef!, scale: 1.0, orientation: self.getImageOrientation(forCamera: self.currentCamera)) + let image = UIImage(cgImage: cgImageRef!, scale: 1.0, orientation: self.orientation.getImageOrientation(forCamera: self.currentCamera)) return image } From a9746bf861e1fbbccf550249830f8517ae187717 Mon Sep 17 00:00:00 2001 From: Jon Andersen Date: Thu, 7 Sep 2017 17:06:21 -0400 Subject: [PATCH 2/5] Ensure preview layer is accessed on main thread --- Source/SwiftyCamViewController.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Source/SwiftyCamViewController.swift b/Source/SwiftyCamViewController.swift index 7a8cc77..9234bdc 100644 --- a/Source/SwiftyCamViewController.swift +++ b/Source/SwiftyCamViewController.swift @@ -503,6 +503,9 @@ open class SwiftyCamViewController: UIViewController { flashView?.alpha = 0.85 previewLayer.addSubview(flashView!) } + + //Must be fetched before on main thread + let previewOrientation = previewLayer.videoPreviewLayer.connection!.videoOrientation sessionQueue.async { [unowned self] in if !movieFileOutput.isRecording { @@ -519,7 +522,7 @@ open class SwiftyCamViewController: UIViewController { movieFileOutputConnection?.isVideoMirrored = true } - movieFileOutputConnection?.videoOrientation = self.orientation.getVideoOrientation() ?? self.previewLayer.videoPreviewLayer.connection!.videoOrientation + movieFileOutputConnection?.videoOrientation = self.orientation.getVideoOrientation() ?? previewOrientation // Start recording to a temporary file. let outputFileName = UUID().uuidString From 0c4b65f8aaac341ab77c803be1fcd544f7899e4c Mon Sep 17 00:00:00 2001 From: Jon Andersen Date: Thu, 7 Sep 2017 17:10:01 -0400 Subject: [PATCH 3/5] Remove protocol for orientation mode --- Source/Orientation.swift | 12 +++--------- Source/SwiftyCamViewController.swift | 8 ++++++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Source/Orientation.swift b/Source/Orientation.swift index de74816..0611d22 100644 --- a/Source/Orientation.swift +++ b/Source/Orientation.swift @@ -17,17 +17,11 @@ import Foundation import AVFoundation import UIKit -protocol Orientation: class { - func getPreviewLayerOrientation() -> AVCaptureVideoOrientation - func getVideoOrientation() -> AVCaptureVideoOrientation? - func getImageOrientation(forCamera: SwiftyCamViewController.CameraSelection) -> UIImageOrientation - func start() - func stop() -} -class DeviceOrientation : Orientation { - var deviceOrientation : UIDeviceOrientation? +class Orientation { + private var deviceOrientation : UIDeviceOrientation? var shouldUseDeviceOrientation: Bool = false + func start() { self.deviceOrientation = UIDevice.current.orientation diff --git a/Source/SwiftyCamViewController.swift b/Source/SwiftyCamViewController.swift index 9234bdc..36c6695 100644 --- a/Source/SwiftyCamViewController.swift +++ b/Source/SwiftyCamViewController.swift @@ -145,7 +145,11 @@ open class SwiftyCamViewController: UIViewController { /// Sets wether the taken photo or video should be oriented according to the device orientation - public var shouldUseDeviceOrientation = false + public var shouldUseDeviceOrientation = false { + didSet { + orientation.shouldUseDeviceOrientation = shouldUseDeviceOrientation + } + } /// Sets whether or not View Controller supports auto rotation @@ -245,7 +249,7 @@ open class SwiftyCamViewController: UIViewController { /// Last changed orientation - var orientation : Orientation = DeviceOrientation() + var orientation = Orientation() /// Boolean to store when View Controller is notified session is running From a95a04a47573db89ec4187c1a0831fd7c37e5acd Mon Sep 17 00:00:00 2001 From: Jon Andersen Date: Thu, 7 Sep 2017 17:21:15 -0400 Subject: [PATCH 4/5] Use core motion to calculate orientation --- Source/Orientation.swift | 42 +++++++++++++++++++++------- Source/SwiftyCamViewController.swift | 3 -- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/Source/Orientation.swift b/Source/Orientation.swift index 0611d22..f51030e 100644 --- a/Source/Orientation.swift +++ b/Source/Orientation.swift @@ -16,29 +16,35 @@ import Foundation import AVFoundation import UIKit +import CoreMotion class Orientation { - private var deviceOrientation : UIDeviceOrientation? + var shouldUseDeviceOrientation: Bool = false - + + fileprivate var deviceOrientation : UIDeviceOrientation? + fileprivate let coreMotionManager = CMMotionManager() + + init() { + coreMotionManager.accelerometerUpdateInterval = 0.1 + } func start() { self.deviceOrientation = UIDevice.current.orientation - NotificationCenter.default.addObserver(self, selector: #selector(didRotate), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) + coreMotionManager.startAccelerometerUpdates(to: .main) { [weak self] (data, error) in + guard let data = data else { + return + } + self?.handleAccelerometerUpdate(data: data) + } } func stop() { - NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) + self.coreMotionManager.stopAccelerometerUpdates() self.deviceOrientation = nil } - @objc func didRotate() { - if !UIDevice.current.orientation.isFlat { - self.deviceOrientation = UIDevice.current.orientation - } - } - func getImageOrientation(forCamera: SwiftyCamViewController.CameraSelection) -> UIImageOrientation { guard shouldUseDeviceOrientation, let deviceOrientation = self.deviceOrientation else { return forCamera == .rear ? .right : .leftMirrored } @@ -82,6 +88,22 @@ class Orientation { return .portrait } } + + private func handleAccelerometerUpdate(data: CMAccelerometerData){ + if(abs(data.acceleration.y) < abs(data.acceleration.x)){ + if(data.acceleration.x > 0){ + deviceOrientation = UIDeviceOrientation.landscapeRight + } else { + deviceOrientation = UIDeviceOrientation.landscapeLeft + } + } else{ + if(data.acceleration.y > 0){ + deviceOrientation = UIDeviceOrientation.portraitUpsideDown + } else { + deviceOrientation = UIDeviceOrientation.portrait + } + } + } } diff --git a/Source/SwiftyCamViewController.swift b/Source/SwiftyCamViewController.swift index 36c6695..d54b78c 100644 --- a/Source/SwiftyCamViewController.swift +++ b/Source/SwiftyCamViewController.swift @@ -781,9 +781,6 @@ open class SwiftyCamViewController: UIViewController { } } - /// Orientation management - - /** Returns a UIImage from Image Data. From 995071a57425b4a0752676317465c00db4e87ede Mon Sep 17 00:00:00 2001 From: Jon Andersen Date: Thu, 7 Sep 2017 17:23:26 -0400 Subject: [PATCH 5/5] Format code --- Source/SwiftyCamViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/SwiftyCamViewController.swift b/Source/SwiftyCamViewController.swift index d54b78c..42eb596 100644 --- a/Source/SwiftyCamViewController.swift +++ b/Source/SwiftyCamViewController.swift @@ -237,7 +237,7 @@ open class SwiftyCamViewController: UIViewController { /// PreviewView for the capture session - var previewLayer : PreviewView! + fileprivate var previewLayer : PreviewView! /// UIView for front facing flash @@ -249,7 +249,7 @@ open class SwiftyCamViewController: UIViewController { /// Last changed orientation - var orientation = Orientation() + fileprivate var orientation : Orientation = Orientation() /// Boolean to store when View Controller is notified session is running