Fix for switching camera from front to back and back to front with addition for iOS 10

This commit is contained in:
Vincent Castro 2017-10-28 19:56:51 -08:00
parent fedb6ef39e
commit afd121afa2

View file

@ -112,7 +112,7 @@ open class SwiftyCamViewController: UIViewController {
/// Sets the maximum zoom scale allowed during gestures gesture /// Sets the maximum zoom scale allowed during gestures gesture
public var maxZoomScale = CGFloat.greatestFiniteMagnitude public var maxZoomScale = CGFloat.greatestFiniteMagnitude
/// Sets whether Tap to Focus and Tap to Adjust Exposure is enabled for the capture session /// Sets whether Tap to Focus and Tap to Adjust Exposure is enabled for the capture session
public var tapToFocus = true public var tapToFocus = true
@ -130,13 +130,13 @@ open class SwiftyCamViewController: UIViewController {
/// Sets whether a double tap to switch cameras is supported /// Sets whether a double tap to switch cameras is supported
public var doubleTapCameraSwitch = true public var doubleTapCameraSwitch = true
/// Sets whether swipe vertically to zoom is supported /// Sets whether swipe vertically to zoom is supported
public var swipeToZoom = true public var swipeToZoom = true
/// Sets whether swipe vertically gestures should be inverted /// Sets whether swipe vertically gestures should be inverted
public var swipeToZoomInverted = false public var swipeToZoomInverted = false
/// Set default launch camera /// Set default launch camera
@ -150,25 +150,25 @@ open class SwiftyCamViewController: UIViewController {
orientation.shouldUseDeviceOrientation = shouldUseDeviceOrientation orientation.shouldUseDeviceOrientation = shouldUseDeviceOrientation
} }
} }
/// Sets whether or not View Controller supports auto rotation /// Sets whether or not View Controller supports auto rotation
public var allowAutoRotate = false public var allowAutoRotate = false
/// Specifies the [videoGravity](https://developer.apple.com/reference/avfoundation/avcapturevideopreviewlayer/1386708-videogravity) for the preview layer. /// Specifies the [videoGravity](https://developer.apple.com/reference/avfoundation/avcapturevideopreviewlayer/1386708-videogravity) for the preview layer.
public var videoGravity : SwiftyCamVideoGravity = .resizeAspect public var videoGravity : SwiftyCamVideoGravity = .resizeAspect
/// Sets whether or not video recordings will record audio /// Sets whether or not video recordings will record audio
/// Setting to true will prompt user for access to microphone on View Controller launch. /// Setting to true will prompt user for access to microphone on View Controller launch.
public var audioEnabled = true public var audioEnabled = true
/// Sets whether or not app should display prompt to app settings if audio/video permission is denied /// Sets whether or not app should display prompt to app settings if audio/video permission is denied
/// If set to false, delegate function will be called to handle exception /// If set to false, delegate function will be called to handle exception
public var shouldPrompToAppSettings = true public var shouldPrompToAppSettings = true
/// Public access to Pinch Gesture /// Public access to Pinch Gesture
fileprivate(set) public var pinchGesture : UIPinchGestureRecognizer! fileprivate(set) public var pinchGesture : UIPinchGestureRecognizer!
/// Public access to Pan Gesture /// Public access to Pan Gesture
fileprivate(set) public var panGesture : UIPanGestureRecognizer! fileprivate(set) public var panGesture : UIPanGestureRecognizer!
@ -242,17 +242,17 @@ open class SwiftyCamViewController: UIViewController {
/// UIView for front facing flash /// UIView for front facing flash
fileprivate var flashView : UIView? fileprivate var flashView : UIView?
/// Pan Translation /// Pan Translation
fileprivate var previousPanTranslation : CGFloat = 0.0 fileprivate var previousPanTranslation : CGFloat = 0.0
/// Last changed orientation /// Last changed orientation
fileprivate var orientation : Orientation = Orientation() fileprivate var orientation : Orientation = Orientation()
/// Boolean to store when View Controller is notified session is running /// Boolean to store when View Controller is notified session is running
fileprivate var sessionRunning = false fileprivate var sessionRunning = false
/// Disable view autorotation for forced portrait recorindg /// Disable view autorotation for forced portrait recorindg
@ -272,7 +272,7 @@ open class SwiftyCamViewController: UIViewController {
view.sendSubview(toBack: previewLayer) view.sendSubview(toBack: previewLayer)
// Add Gesture Recognizers // Add Gesture Recognizers
addGestureRecognizers() addGestureRecognizers()
previewLayer.session = session previewLayer.session = session
@ -303,72 +303,72 @@ open class SwiftyCamViewController: UIViewController {
self.configureSession() self.configureSession()
} }
} }
// MARK: ViewDidLayoutSubviews // MARK: ViewDidLayoutSubviews
/// ViewDidLayoutSubviews() Implementation /// ViewDidLayoutSubviews() Implementation
private func updatePreviewLayer(layer: AVCaptureConnection, orientation: AVCaptureVideoOrientation) { private func updatePreviewLayer(layer: AVCaptureConnection, orientation: AVCaptureVideoOrientation) {
layer.videoOrientation = orientation layer.videoOrientation = orientation
previewLayer.frame = self.view.bounds previewLayer.frame = self.view.bounds
} }
override open func viewDidLayoutSubviews() { override open func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews() super.viewDidLayoutSubviews()
if let connection = self.previewLayer?.videoPreviewLayer.connection { if let connection = self.previewLayer?.videoPreviewLayer.connection {
let currentDevice: UIDevice = UIDevice.current let currentDevice: UIDevice = UIDevice.current
let orientation: UIDeviceOrientation = currentDevice.orientation let orientation: UIDeviceOrientation = currentDevice.orientation
let previewLayerConnection : AVCaptureConnection = connection let previewLayerConnection : AVCaptureConnection = connection
if previewLayerConnection.isVideoOrientationSupported { if previewLayerConnection.isVideoOrientationSupported {
switch (orientation) { switch (orientation) {
case .portrait: updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait) case .portrait: updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait)
break break
case .landscapeRight: updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeLeft) case .landscapeRight: updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeLeft)
break break
case .landscapeLeft: updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeRight) case .landscapeLeft: updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeRight)
break break
case .portraitUpsideDown: updatePreviewLayer(layer: previewLayerConnection, orientation: .portraitUpsideDown) case .portraitUpsideDown: updatePreviewLayer(layer: previewLayerConnection, orientation: .portraitUpsideDown)
break break
default: updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait) default: updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait)
break break
} }
} }
} }
} }
// MARK: ViewWillAppear // MARK: ViewWillAppear
/// ViewWillAppear(_ animated:) Implementation /// ViewWillAppear(_ animated:) Implementation
open override func viewWillAppear(_ animated: Bool) { open override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(captureSessionDidStartRunning), name: .AVCaptureSessionDidStartRunning, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(captureSessionDidStartRunning), name: .AVCaptureSessionDidStartRunning, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(captureSessionDidStopRunning), name: .AVCaptureSessionDidStopRunning, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(captureSessionDidStopRunning), name: .AVCaptureSessionDidStopRunning, object: nil)
} }
// MARK: ViewDidAppear // MARK: ViewDidAppear
/// ViewDidAppear(_ animated:) Implementation /// ViewDidAppear(_ animated:) Implementation
override open func viewDidAppear(_ animated: Bool) { override open func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)
// Subscribe to device rotation notifications // Subscribe to device rotation notifications
if shouldUseDeviceOrientation { if shouldUseDeviceOrientation {
@ -385,12 +385,12 @@ open class SwiftyCamViewController: UIViewController {
// Begin Session // Begin Session
self.session.startRunning() self.session.startRunning()
self.isSessionRunning = self.session.isRunning self.isSessionRunning = self.session.isRunning
// Preview layer video orientation can be set only after the connection is created // Preview layer video orientation can be set only after the connection is created
DispatchQueue.main.async { DispatchQueue.main.async {
self.previewLayer.videoPreviewLayer.connection?.videoOrientation = self.orientation.getPreviewLayerOrientation() self.previewLayer.videoPreviewLayer.connection?.videoOrientation = self.orientation.getPreviewLayerOrientation()
} }
case .notAuthorized: case .notAuthorized:
if self.shouldPrompToAppSettings == true { if self.shouldPrompToAppSettings == true {
self.promptToAppSettings() self.promptToAppSettings()
@ -413,7 +413,7 @@ open class SwiftyCamViewController: UIViewController {
override open func viewDidDisappear(_ animated: Bool) { override open func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated) super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)
sessionRunning = false sessionRunning = false
@ -447,7 +447,7 @@ open class SwiftyCamViewController: UIViewController {
guard let device = videoDevice else { guard let device = videoDevice else {
return return
} }
if device.hasFlash == true && flashEnabled == true /* TODO: Add Support for Retina Flash and add front flash */ { if device.hasFlash == true && flashEnabled == true /* TODO: Add Support for Retina Flash and add front flash */ {
changeFlashSettings(device: device, mode: .on) changeFlashSettings(device: device, mode: .on)
@ -488,7 +488,7 @@ open class SwiftyCamViewController: UIViewController {
*/ */
public func startVideoRecording() { public func startVideoRecording() {
guard sessionRunning == true else { guard sessionRunning == true else {
print("[SwiftyCam]: Cannot start video recoding. Capture session is not running") print("[SwiftyCam]: Cannot start video recoding. Capture session is not running")
return return
@ -507,7 +507,7 @@ open class SwiftyCamViewController: UIViewController {
flashView?.alpha = 0.85 flashView?.alpha = 0.85
previewLayer.addSubview(flashView!) previewLayer.addSubview(flashView!)
} }
//Must be fetched before on main thread //Must be fetched before on main thread
let previewOrientation = previewLayer.videoPreviewLayer.connection!.videoOrientation let previewOrientation = previewLayer.videoPreviewLayer.connection!.videoOrientation
@ -587,11 +587,11 @@ open class SwiftyCamViewController: UIViewController {
print("[SwiftyCam]: Switching between cameras while recording video is not supported") print("[SwiftyCam]: Switching between cameras while recording video is not supported")
return return
} }
guard session.isRunning == true else { guard session.isRunning == true else {
return return
} }
switch currentCamera { switch currentCamera {
case .front: case .front:
currentCamera = .rear currentCamera = .rear
@ -803,12 +803,12 @@ open class SwiftyCamViewController: UIViewController {
} }
fileprivate func capturePhotoAsyncronously(completionHandler: @escaping(Bool) -> ()) { fileprivate func capturePhotoAsyncronously(completionHandler: @escaping(Bool) -> ()) {
guard sessionRunning == true else { guard sessionRunning == true else {
print("[SwiftyCam]: Cannot take photo. Capture session is not running") print("[SwiftyCam]: Cannot take photo. Capture session is not running")
return return
} }
if let videoConnection = photoFileOutput?.connection(with: AVMediaType.video) { if let videoConnection = photoFileOutput?.connection(with: AVMediaType.video) {
photoFileOutput?.captureStillImageAsynchronously(from: videoConnection, completionHandler: {(sampleBuffer, error) in photoFileOutput?.captureStillImageAsynchronously(from: videoConnection, completionHandler: {(sampleBuffer, error) in
@ -885,7 +885,26 @@ open class SwiftyCamViewController: UIViewController {
/// Get Devices /// Get Devices
fileprivate class func deviceWithMediaType(_ mediaType: String, preferringPosition position: AVCaptureDevice.Position) -> AVCaptureDevice? { fileprivate class func deviceWithMediaType(_ mediaType: String, preferringPosition position: AVCaptureDevice.Position) -> AVCaptureDevice? {
return AVCaptureDevice.devices(for: AVMediaType(rawValue: mediaType)).first if #available(iOS 10.0, *) {
let avDevice = AVCaptureDevice.default(AVCaptureDevice.DeviceType.builtInWideAngleCamera, for: AVMediaType(rawValue: mediaType), position: position)
return avDevice
} else {
// Fallback on earlier versions
let avDevice = AVCaptureDevice.devices(for: AVMediaType(rawValue: mediaType))
var avDeviceNum = 0
for device in avDevice {
print("deviceWithMediaType Position: \(device.position.rawValue)")
if device.position == position {
break
} else {
avDeviceNum += 1
}
}
return avDevice[avDeviceNum]
}
//return AVCaptureDevice.devices(for: AVMediaType(rawValue: mediaType), position: position).first
} }
/// Enable or disable flash for photo /// Enable or disable flash for photo
@ -953,7 +972,7 @@ open class SwiftyCamViewController: UIViewController {
guard allowBackgroundAudio == true else { guard allowBackgroundAudio == true else {
return return
} }
guard audioEnabled == true else { guard audioEnabled == true else {
return return
} }
@ -973,16 +992,16 @@ open class SwiftyCamViewController: UIViewController {
} }
} }
/// Called when Notification Center registers session starts running /// Called when Notification Center registers session starts running
@objc private func captureSessionDidStartRunning() { @objc private func captureSessionDidStartRunning() {
sessionRunning = true sessionRunning = true
DispatchQueue.main.async { DispatchQueue.main.async {
self.cameraDelegate?.swiftyCamSessionDidStartRunning(self) self.cameraDelegate?.swiftyCamSessionDidStartRunning(self)
} }
} }
/// Called when Notification Center registers session stops running /// Called when Notification Center registers session stops running
@objc private func captureSessionDidStopRunning() { @objc private func captureSessionDidStopRunning() {
@ -1032,16 +1051,16 @@ extension SwiftyCamViewController : SwiftyCamButtonDelegate {
extension SwiftyCamViewController : AVCaptureFileOutputRecordingDelegate { extension SwiftyCamViewController : AVCaptureFileOutputRecordingDelegate {
/// Process newly captured video and write it to temporary directory /// Process newly captured video and write it to temporary directory
public func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) { public func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
if let currentBackgroundRecordingID = backgroundRecordingID { if let currentBackgroundRecordingID = backgroundRecordingID {
backgroundRecordingID = UIBackgroundTaskInvalid backgroundRecordingID = UIBackgroundTaskInvalid
if currentBackgroundRecordingID != UIBackgroundTaskInvalid { if currentBackgroundRecordingID != UIBackgroundTaskInvalid {
UIApplication.shared.endBackgroundTask(currentBackgroundRecordingID) UIApplication.shared.endBackgroundTask(currentBackgroundRecordingID)
} }
} }
if let currentError = error { if let currentError = error {
print("[SwiftyCam]: Movie file finishing error: \(currentError)") print("[SwiftyCam]: Movie file finishing error: \(currentError)")
DispatchQueue.main.async { DispatchQueue.main.async {
@ -1064,7 +1083,7 @@ extension SwiftyCamViewController {
@objc fileprivate func zoomGesture(pinch: UIPinchGestureRecognizer) { @objc fileprivate func zoomGesture(pinch: UIPinchGestureRecognizer) {
guard pinchToZoom == true && self.currentCamera == .rear else { guard pinchToZoom == true && self.currentCamera == .rear else {
//ignore pinch //ignore pinch
return return
} }
do { do {
@ -1132,42 +1151,42 @@ extension SwiftyCamViewController {
} }
switchCamera() switchCamera()
} }
@objc private func panGesture(pan: UIPanGestureRecognizer) { @objc private func panGesture(pan: UIPanGestureRecognizer) {
guard swipeToZoom == true && self.currentCamera == .rear else { guard swipeToZoom == true && self.currentCamera == .rear else {
//ignore pan //ignore pan
return return
} }
let currentTranslation = pan.translation(in: view).y let currentTranslation = pan.translation(in: view).y
let translationDifference = currentTranslation - previousPanTranslation let translationDifference = currentTranslation - previousPanTranslation
do { do {
let captureDevice = AVCaptureDevice.devices().first let captureDevice = AVCaptureDevice.devices().first
try captureDevice?.lockForConfiguration() try captureDevice?.lockForConfiguration()
let currentZoom = captureDevice?.videoZoomFactor ?? 0.0 let currentZoom = captureDevice?.videoZoomFactor ?? 0.0
if swipeToZoomInverted == true { if swipeToZoomInverted == true {
zoomScale = min(maxZoomScale, max(1.0, min(currentZoom - (translationDifference / 75), captureDevice!.activeFormat.videoMaxZoomFactor))) zoomScale = min(maxZoomScale, max(1.0, min(currentZoom - (translationDifference / 75), captureDevice!.activeFormat.videoMaxZoomFactor)))
} else { } else {
zoomScale = min(maxZoomScale, max(1.0, min(currentZoom + (translationDifference / 75), captureDevice!.activeFormat.videoMaxZoomFactor))) zoomScale = min(maxZoomScale, max(1.0, min(currentZoom + (translationDifference / 75), captureDevice!.activeFormat.videoMaxZoomFactor)))
} }
captureDevice?.videoZoomFactor = zoomScale captureDevice?.videoZoomFactor = zoomScale
// Call Delegate function with current zoom scale // Call Delegate function with current zoom scale
DispatchQueue.main.async { DispatchQueue.main.async {
self.cameraDelegate?.swiftyCam(self, didChangeZoomLevel: self.zoomScale) self.cameraDelegate?.swiftyCam(self, didChangeZoomLevel: self.zoomScale)
} }
captureDevice?.unlockForConfiguration() captureDevice?.unlockForConfiguration()
} catch { } catch {
print("[SwiftyCam]: Error locking configuration") print("[SwiftyCam]: Error locking configuration")
} }
if pan.state == .ended || pan.state == .failed || pan.state == .cancelled { if pan.state == .ended || pan.state == .failed || pan.state == .cancelled {
previousPanTranslation = 0.0 previousPanTranslation = 0.0
} else { } else {
@ -1196,7 +1215,7 @@ extension SwiftyCamViewController {
doubleTapGesture.numberOfTapsRequired = 2 doubleTapGesture.numberOfTapsRequired = 2
doubleTapGesture.delegate = self doubleTapGesture.delegate = self
previewLayer.addGestureRecognizer(doubleTapGesture) previewLayer.addGestureRecognizer(doubleTapGesture)
panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGesture(pan:))) panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGesture(pan:)))
panGesture.delegate = self panGesture.delegate = self
previewLayer.addGestureRecognizer(panGesture) previewLayer.addGestureRecognizer(panGesture)
@ -1217,7 +1236,3 @@ extension SwiftyCamViewController : UIGestureRecognizerDelegate {
return true return true
} }
} }