diff --git a/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/project.pbxproj b/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/project.pbxproj index d020416..814ab8d 100644 --- a/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/project.pbxproj +++ b/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/project.pbxproj @@ -198,6 +198,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -300,7 +301,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.Cappsule.SwiftyCam-iOS"; PRODUCT_NAME = SwiftyCam; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -323,7 +324,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.Cappsule.SwiftyCam-iOS"; PRODUCT_NAME = SwiftyCam; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -384,7 +385,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -434,7 +435,7 @@ SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; }; name = Release; @@ -450,7 +451,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.Walzy.DemoSwiftyCam1; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -466,7 +467,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.Walzy.DemoSwiftyCam1; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; diff --git a/DemoSwiftyCam/DemoSwiftyCam/AppDelegate.swift b/DemoSwiftyCam/DemoSwiftyCam/AppDelegate.swift index dfee801..322b74d 100644 --- a/DemoSwiftyCam/DemoSwiftyCam/AppDelegate.swift +++ b/DemoSwiftyCam/DemoSwiftyCam/AppDelegate.swift @@ -22,7 +22,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } diff --git a/DemoSwiftyCam/DemoSwiftyCam/PhotoViewController.swift b/DemoSwiftyCam/DemoSwiftyCam/PhotoViewController.swift index 4333a1f..82e2f4d 100644 --- a/DemoSwiftyCam/DemoSwiftyCam/PhotoViewController.swift +++ b/DemoSwiftyCam/DemoSwiftyCam/PhotoViewController.swift @@ -36,11 +36,11 @@ class PhotoViewController: UIViewController { super.viewDidLoad() self.view.backgroundColor = UIColor.gray let backgroundImageView = UIImageView(frame: view.frame) - backgroundImageView.contentMode = UIViewContentMode.scaleAspectFit + backgroundImageView.contentMode = UIView.ContentMode.scaleAspectFit backgroundImageView.image = backgroundImage view.addSubview(backgroundImageView) let cancelButton = UIButton(frame: CGRect(x: 10.0, y: 10.0, width: 30.0, height: 30.0)) - cancelButton.setImage(#imageLiteral(resourceName: "cancel"), for: UIControlState()) + cancelButton.setImage(#imageLiteral(resourceName: "cancel"), for: UIControl.State()) cancelButton.addTarget(self, action: #selector(cancel), for: .touchUpInside) view.addSubview(cancelButton) } diff --git a/DemoSwiftyCam/DemoSwiftyCam/VideoViewController.swift b/DemoSwiftyCam/DemoSwiftyCam/VideoViewController.swift index 0423a71..2befa16 100644 --- a/DemoSwiftyCam/DemoSwiftyCam/VideoViewController.swift +++ b/DemoSwiftyCam/DemoSwiftyCam/VideoViewController.swift @@ -49,20 +49,20 @@ class VideoViewController: UIViewController { playerController!.showsPlaybackControls = false playerController!.player = player! - self.addChildViewController(playerController!) + self.addChild(playerController!) self.view.addSubview(playerController!.view) playerController!.view.frame = view.frame NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.player!.currentItem) let cancelButton = UIButton(frame: CGRect(x: 10.0, y: 10.0, width: 30.0, height: 30.0)) - cancelButton.setImage(#imageLiteral(resourceName: "cancel"), for: UIControlState()) + cancelButton.setImage(#imageLiteral(resourceName: "cancel"), for: UIControl.State()) cancelButton.addTarget(self, action: #selector(cancel), for: .touchUpInside) view.addSubview(cancelButton) // Allow background audio to continue to play do { - try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient) + try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient) } catch let error as NSError { print(error) } @@ -85,7 +85,7 @@ class VideoViewController: UIViewController { @objc fileprivate func playerItemDidReachEnd(_ notification: Notification) { if self.player != nil { - self.player!.seek(to: kCMTimeZero) + self.player!.seek(to: CMTime.zero) self.player!.play() } } diff --git a/DemoSwiftyCam/DemoSwiftyCam/ViewController.swift b/DemoSwiftyCam/DemoSwiftyCam/ViewController.swift index 67b2b8f..06a5d9b 100644 --- a/DemoSwiftyCam/DemoSwiftyCam/ViewController.swift +++ b/DemoSwiftyCam/DemoSwiftyCam/ViewController.swift @@ -153,9 +153,9 @@ extension ViewController { fileprivate func toggleFlashAnimation() { if flashEnabled == true { - flashButton.setImage(#imageLiteral(resourceName: "flash"), for: UIControlState()) + flashButton.setImage(#imageLiteral(resourceName: "flash"), for: UIControl.State()) } else { - flashButton.setImage(#imageLiteral(resourceName: "flashOutline"), for: UIControlState()) + flashButton.setImage(#imageLiteral(resourceName: "flashOutline"), for: UIControl.State()) } } } diff --git a/Source/Orientation.swift b/Source/Orientation.swift index f51030e..578d475 100644 --- a/Source/Orientation.swift +++ b/Source/Orientation.swift @@ -45,7 +45,7 @@ class Orientation { self.deviceOrientation = nil } - func getImageOrientation(forCamera: SwiftyCamViewController.CameraSelection) -> UIImageOrientation { + func getImageOrientation(forCamera: SwiftyCamViewController.CameraSelection) -> UIImage.Orientation { guard shouldUseDeviceOrientation, let deviceOrientation = self.deviceOrientation else { return forCamera == .rear ? .right : .leftMirrored } switch deviceOrientation { diff --git a/Source/SwiftyCamViewController.swift b/Source/SwiftyCamViewController.swift index bb48b20..ce7eb1f 100644 --- a/Source/SwiftyCamViewController.swift +++ b/Source/SwiftyCamViewController.swift @@ -23,9 +23,9 @@ import AVFoundation open class SwiftyCamViewController: UIViewController { - // MARK: Enumeration Declaration + // MARK: Enumeration Declaration - /// Enumeration for Camera Selection + /// Enumeration for Camera Selection public enum CameraSelection: String { @@ -36,100 +36,100 @@ open class SwiftyCamViewController: UIViewController { case front = "front" } - /// Enumeration for video quality of the capture session. Corresponds to a AVCaptureSessionPreset + /// Enumeration for video quality of the capture session. Corresponds to a AVCaptureSessionPreset - public enum VideoQuality { + public enum VideoQuality { - /// AVCaptureSessionPresetHigh - case high + /// AVCaptureSessionPresetHigh + case high - /// AVCaptureSessionPresetMedium - case medium + /// AVCaptureSessionPresetMedium + case medium - /// AVCaptureSessionPresetLow - case low + /// AVCaptureSessionPresetLow + case low - /// AVCaptureSessionPreset352x288 - case resolution352x288 + /// AVCaptureSessionPreset352x288 + case resolution352x288 - /// AVCaptureSessionPreset640x480 - case resolution640x480 + /// AVCaptureSessionPreset640x480 + case resolution640x480 - /// AVCaptureSessionPreset1280x720 - case resolution1280x720 + /// AVCaptureSessionPreset1280x720 + case resolution1280x720 - /// AVCaptureSessionPreset1920x1080 - case resolution1920x1080 + /// AVCaptureSessionPreset1920x1080 + case resolution1920x1080 - /// AVCaptureSessionPreset3840x2160 - case resolution3840x2160 + /// AVCaptureSessionPreset3840x2160 + case resolution3840x2160 - /// AVCaptureSessionPresetiFrame960x540 - case iframe960x540 + /// AVCaptureSessionPresetiFrame960x540 + case iframe960x540 - /// AVCaptureSessionPresetiFrame1280x720 - case iframe1280x720 - } + /// AVCaptureSessionPresetiFrame1280x720 + case iframe1280x720 + } - /** + /** - Result from the AVCaptureSession Setup + Result from the AVCaptureSession Setup - - success: success - - notAuthorized: User denied access to Camera of Microphone - - configurationFailed: Unknown error - */ + - success: success + - notAuthorized: User denied access to Camera of Microphone + - configurationFailed: Unknown error + */ - fileprivate enum SessionSetupResult { - case success - case notAuthorized - case configurationFailed - } + fileprivate enum SessionSetupResult { + case success + case notAuthorized + case configurationFailed + } - // MARK: Public Variable Declarations + // MARK: Public Variable Declarations - /// Public Camera Delegate for the Custom View Controller Subclass + /// Public Camera Delegate for the Custom View Controller Subclass - public weak var cameraDelegate: SwiftyCamViewControllerDelegate? + public weak var cameraDelegate: SwiftyCamViewControllerDelegate? - /// Maxiumum video duration if SwiftyCamButton is used + /// Maxiumum video duration if SwiftyCamButton is used - public var maximumVideoDuration : Double = 0.0 + public var maximumVideoDuration : Double = 0.0 - /// Video capture quality + /// Video capture quality - public var videoQuality : VideoQuality = .high + public var videoQuality : VideoQuality = .high - /// Sets whether flash is enabled for photo and video capture + /// Sets whether flash is enabled for photo and video capture - public var flashEnabled = false + public var flashEnabled = false - /// Sets whether Pinch to Zoom is enabled for the capture session + /// Sets whether Pinch to Zoom is enabled for the capture session - public var pinchToZoom = true + public var pinchToZoom = true - /// 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 - /// Sets whether the capture session should adjust to low light conditions automatically - /// - /// Only supported on iPhone 5 and 5C + /// Sets whether the capture session should adjust to low light conditions automatically + /// + /// Only supported on iPhone 5 and 5C - public var lowLightBoost = true + public var lowLightBoost = true - /// Set whether SwiftyCam should allow background audio from other applications + /// Set whether SwiftyCam should allow background audio from other applications - public var allowBackgroundAudio = true + public var allowBackgroundAudio = true - /// 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 @@ -139,11 +139,11 @@ open class SwiftyCamViewController: UIViewController { public var swipeToZoomInverted = false - /// Set default launch camera + /// Set default launch camera - public var defaultCamera = CameraSelection.rear + public var defaultCamera = CameraSelection.rear - /// Sets wether the taken photo or video should be oriented according to the device orientation + /// Sets wether the taken photo or video should be oriented according to the device orientation public var shouldUseDeviceOrientation = false { didSet { @@ -161,6 +161,9 @@ open class SwiftyCamViewController: UIViewController { /// Sets whether or not video recordings will record audio /// Setting to true will prompt user for access to microphone on View Controller launch. public var audioEnabled = true + + // Should it use the permission alert? + public var usePermissionAlert = true /// 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 @@ -173,81 +176,106 @@ open class SwiftyCamViewController: UIViewController { fileprivate(set) public var panGesture : UIPanGestureRecognizer! - // MARK: Public Get-only Variable Declarations + // MARK: Public Get-only Variable Declarations + + /// Returns true if video is currently being recorded + + private(set) public var isVideoRecording = false - /// Returns true if video is currently being recorded + /// Returns true if the capture session is currently running - private(set) public var isVideoRecording = false + private(set) public var isSessionRunning = false - /// Returns true if the capture session is currently running + /// Returns the CameraSelection corresponding to the currently utilized camera - private(set) public var isSessionRunning = false + private(set) public var currentCamera = CameraSelection.rear - /// Returns the CameraSelection corresponding to the currently utilized camera + // MARK: Private Constant Declarations - private(set) public var currentCamera = CameraSelection.rear + /// Current Capture Session - // MARK: Private Constant Declarations + public let session = AVCaptureSession() - /// Current Capture Session + /// Serial queue used for setting up session - public let session = AVCaptureSession() + fileprivate let sessionQueue = DispatchQueue(label: "session queue", attributes: []) - /// Serial queue used for setting up session + // MARK: Private Variable Declarations - fileprivate let sessionQueue = DispatchQueue(label: "session queue", attributes: []) + /// Variable for storing current zoom scale - // MARK: Private Variable Declarations + fileprivate var zoomScale = CGFloat(1.0) - /// Variable for storing current zoom scale + /// Variable for storing initial zoom scale before Pinch to Zoom begins - fileprivate var zoomScale = CGFloat(1.0) + fileprivate var beginZoomScale = CGFloat(1.0) - /// Variable for storing initial zoom scale before Pinch to Zoom begins + /// Returns true if the torch (flash) is currently enabled - fileprivate var beginZoomScale = CGFloat(1.0) + fileprivate var isCameraTorchOn = false - /// Returns true if the torch (flash) is currently enabled + /// Variable to store result of capture session setup - fileprivate var isCameraTorchOn = false + fileprivate var setupResult = SessionSetupResult.success - /// Variable to store result of capture session setup + fileprivate lazy var permissionErrorLabel : UILabel = { + let label = UILabel() + label.textColor = UIColor.white + label.font = UIFont.systemFont(ofSize: 17) + label.numberOfLines = 2 + label.textAlignment = .center + label.text = "Please enable this app's\ncamera permissions." + label.translatesAutoresizingMaskIntoConstraints = false + label.isHidden = true + return label + }() - fileprivate var setupResult = SessionSetupResult.success + fileprivate lazy var permissionErrorButton : UIButton = { + let button = UIButton() + button.setTitleColor(UIColor.white, for: .normal) + button.setTitle("Open Settings", for: .normal) + button.titleLabel?.font = UIFont.systemFont(ofSize: 14) + button.layer.borderColor = UIColor.white.cgColor + button.layer.borderWidth = 1 + button.translatesAutoresizingMaskIntoConstraints = false + button.isHidden = true + button.addTarget(self, action: #selector(openSettings), for: .touchUpInside) + return button + }() - /// BackgroundID variable for video recording + /// BackgroundID variable for video recording - fileprivate var backgroundRecordingID : UIBackgroundTaskIdentifier? = nil + fileprivate var backgroundRecordingID : UIBackgroundTaskIdentifier? = nil - /// Video Input variable + /// Video Input variable - fileprivate var videoDeviceInput : AVCaptureDeviceInput! + fileprivate var videoDeviceInput : AVCaptureDeviceInput! - /// Movie File Output variable + /// Movie File Output variable - fileprivate var movieFileOutput : AVCaptureMovieFileOutput? + fileprivate var movieFileOutput : AVCaptureMovieFileOutput? - /// Photo File Output variable + /// Photo File Output variable - fileprivate var photoFileOutput : AVCaptureStillImageOutput? + fileprivate var photoFileOutput : AVCaptureStillImageOutput? - /// Video Device variable + /// Video Device variable - fileprivate var videoDevice : AVCaptureDevice? + fileprivate var videoDevice : AVCaptureDevice? - /// PreviewView for the capture session + /// PreviewView for the capture session - fileprivate var previewLayer : PreviewView! + fileprivate var previewLayer : PreviewView! - /// UIView for front facing flash + /// UIView for front facing flash - fileprivate var flashView : UIView? + fileprivate var flashView : UIView? /// Pan Translation fileprivate var previousPanTranslation : CGFloat = 0.0 - /// Last changed orientation + /// Last changed orientation fileprivate var orientation : Orientation = Orientation() @@ -255,54 +283,84 @@ open class SwiftyCamViewController: UIViewController { fileprivate var sessionRunning = false - /// Disable view autorotation for forced portrait recorindg + /// Disable view autorotation for forced portrait recorindg - override open var shouldAutorotate: Bool { - return allowAutoRotate - } + override open var shouldAutorotate: Bool { + return allowAutoRotate + } - // MARK: ViewDidLoad + // makes camera full screen, no bleed or borders + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.videoGravity = .resizeAspectFill + } - /// ViewDidLoad Implementation + // MARK: ViewDidLoad - override open func viewDidLoad() { - super.viewDidLoad() + /// ViewDidLoad Implementation + + override open func viewDidLoad() { + super.viewDidLoad() previewLayer = PreviewView(frame: view.frame, videoGravity: videoGravity) + previewLayer.center = view.center view.addSubview(previewLayer) - view.sendSubview(toBack: previewLayer) + view.sendSubviewToBack(previewLayer) + + // adds error label and button if camera permissions are denied + view.addSubview(permissionErrorLabel) + view.addSubview(permissionErrorButton) + + // centers error label and button + if #available(iOS 9.0, *) { + NSLayoutConstraint.activate( + [ + permissionErrorLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor), + permissionErrorLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor), + permissionErrorLabel.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 15), + permissionErrorLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -15), + permissionErrorLabel.heightAnchor.constraint(equalToConstant: 50), + permissionErrorButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), + permissionErrorButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 55), + permissionErrorButton.heightAnchor.constraint(equalToConstant: 30), + permissionErrorButton.widthAnchor.constraint(equalToConstant: 150) + ] + ) + } else { + // Fallback on earlier versions + } - // Add Gesture Recognizers + // Add Gesture Recognizers addGestureRecognizers() - previewLayer.session = session - - // Test authorization status for Camera and Micophone - - switch AVCaptureDevice.authorizationStatus(for: AVMediaType.video) { - case .authorized: - - // already authorized - break - case .notDetermined: - - // not yet determined - sessionQueue.suspend() - AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { [unowned self] granted in - if !granted { - self.setupResult = .notAuthorized - } - self.sessionQueue.resume() - }) - default: - - // already been asked. Denied access - setupResult = .notAuthorized - } - sessionQueue.async { [unowned self] in - self.configureSession() - } - } + previewLayer.session = session + + // Test authorization status for Camera and Micophone + + switch AVCaptureDevice.authorizationStatus(for: AVMediaType.video) { + case .authorized: + + // already authorized + break + case .notDetermined: + + // not yet determined + sessionQueue.suspend() + AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { [unowned self] granted in + if !granted { + self.setupResult = .notAuthorized + } + self.sessionQueue.resume() + }) + default: + + // already been asked. Denied access + setupResult = .notAuthorized + } + sessionQueue.async { [unowned self] in + self.configureSession() + } + } // MARK: ViewDidLayoutSubviews @@ -363,712 +421,679 @@ open class SwiftyCamViewController: UIViewController { NotificationCenter.default.addObserver(self, selector: #selector(captureSessionDidStopRunning), name: .AVCaptureSessionDidStopRunning, object: nil) } - // MARK: ViewDidAppear - - /// ViewDidAppear(_ animated:) Implementation - override open func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) + // MARK: ViewDidAppear + /// ViewDidAppear(_ animated:) Implementation + override open func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) - // Subscribe to device rotation notifications + // Subscribe to device rotation notifications - if shouldUseDeviceOrientation { - orientation.start() - } + if shouldUseDeviceOrientation { + orientation.start() + } - // Set background audio preference + // Set background audio preference - setBackgroundAudioPreference() + setBackgroundAudioPreference() - sessionQueue.async { - switch self.setupResult { - case .success: - // Begin Session - self.session.startRunning() - self.isSessionRunning = self.session.isRunning + sessionQueue.async { + switch self.setupResult { + case .success: + // Begin Session + self.session.startRunning() + self.isSessionRunning = self.session.isRunning // Preview layer video orientation can be set only after the connection is created DispatchQueue.main.async { self.previewLayer.videoPreviewLayer.connection?.videoOrientation = self.orientation.getPreviewLayerOrientation() } - case .notAuthorized: + case .notAuthorized: + // Prompt to App Settings if self.shouldPrompToAppSettings == true { self.promptToAppSettings() } else { self.cameraDelegate?.swiftyCamNotAuthorized(self) } - case .configurationFailed: - // Unknown Error - DispatchQueue.main.async { - self.cameraDelegate?.swiftyCamDidFailToConfigure(self) - } - } - } - } + case .configurationFailed: + // Unknown Error + DispatchQueue.main.async(execute: { [unowned self] in + let message = NSLocalizedString("Unable to capture media", comment: "Alert message when something goes wrong during capture session configuration") + let alertController = UIAlertController(title: "AVCam", message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil)) + self.present(alertController, animated: true, completion: nil) + }) + } + } + } - // MARK: ViewDidDisappear + // MARK: ViewDidDisappear - /// ViewDidDisappear(_ animated:) Implementation + /// ViewDidDisappear(_ animated:) Implementation - override open func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) + override open func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) - NotificationCenter.default.removeObserver(self) - sessionRunning = false + // If session is running, stop the session +// if self.isSessionRunning == true { +// self.session.stopRunning() +// self.isSessionRunning = false +// } - // If session is running, stop the session - if self.isSessionRunning == true { - self.session.stopRunning() - self.isSessionRunning = false - } + //Disble flash if it is currently enabled + disableFlash() - //Disble flash if it is currently enabled - disableFlash() + // Unsubscribe from device rotation notifications +// if shouldUseDeviceOrientation { +// unsubscribeFromDeviceOrientationChangeNotifications() +// } + } - // Unsubscribe from device rotation notifications - if shouldUseDeviceOrientation { - orientation.stop() - } - } + // MARK: Public Functions - // MARK: Public Functions + /** - /** + Capture photo from current session - Capture photo from current session + UIImage will be returned with the SwiftyCamViewControllerDelegate function SwiftyCamDidTakePhoto(photo:) - UIImage will be returned with the SwiftyCamViewControllerDelegate function SwiftyCamDidTakePhoto(photo:) + */ +} - */ - public func takePhoto() { +// MARK: uknown +extension SwiftyCamViewController { - guard let device = videoDevice else { - return - } + @objc private func captureSessionDidStartRunning() { + self.sessionRunning = true + DispatchQueue.main.async(execute: { [unowned self] in + self.cameraDelegate?.swiftyCamSessionDidStartRunning(self) + }) + } + + /// Called when Notification Center registers session stops running + + @objc private func captureSessionDidStopRunning() { + self.sessionRunning = false + DispatchQueue.main.async(execute: { [unowned self] in + self.cameraDelegate?.swiftyCamSessionDidStopRunning(self) + }) + } +} - if device.hasFlash == true && flashEnabled == true /* TODO: Add Support for Retina Flash and add front flash */ { - changeFlashSettings(device: device, mode: .on) - capturePhotoAsyncronously(completionHandler: { (_) in }) +extension SwiftyCamViewController : SwiftyCamButtonDelegate { - } else if device.hasFlash == false && flashEnabled == true && currentCamera == .front { - flashView = UIView(frame: view.frame) - flashView?.alpha = 0.0 - flashView?.backgroundColor = UIColor.white - previewLayer.addSubview(flashView!) + /// Sets the maximum duration of the SwiftyCamButton - UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveEaseInOut, animations: { - self.flashView?.alpha = 1.0 + public func setMaxiumVideoDuration() -> Double { + return maximumVideoDuration + } - }, completion: { (_) in - self.capturePhotoAsyncronously(completionHandler: { (success) in - UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveEaseInOut, animations: { - self.flashView?.alpha = 0.0 - }, completion: { (_) in - self.flashView?.removeFromSuperview() - }) - }) - }) - } else { - if device.isFlashActive == true { - changeFlashSettings(device: device, mode: .off) - } - capturePhotoAsyncronously(completionHandler: { (_) in }) - } - } + /// Set UITapGesture to take photo - /** + public func buttonWasTapped() { + takePhoto() + } - Begin recording video of current session + /// Set UILongPressGesture start to begin video - SwiftyCamViewControllerDelegate function SwiftyCamDidBeginRecordingVideo() will be called + public func buttonDidBeginLongPress() { + startVideoRecording() + } - */ + /// Set UILongPressGesture begin to begin end video - public func startVideoRecording() { - guard sessionRunning == true else { - print("[SwiftyCam]: Cannot start video recoding. Capture session is not running") - return - } - guard let movieFileOutput = self.movieFileOutput else { - return - } + public func buttonDidEndLongPress() { + stopVideoRecording() + } - if currentCamera == .rear && flashEnabled == true { - enableFlash() - } + /// Called if maximum duration is reached - if currentCamera == .front && flashEnabled == true { - flashView = UIView(frame: view.frame) - flashView?.backgroundColor = UIColor.white - flashView?.alpha = 0.85 - previewLayer.addSubview(flashView!) - } + public func longPressDidReachMaximumDuration() { + stopVideoRecording() + } +} +// MARK: Camera Functions +extension SwiftyCamViewController { + + public func takePhoto() { + + guard let device = videoDevice else { + return + } + + if device.hasFlash == true && flashEnabled == true /* TODO: Add Support for Retina Flash and add front flash */ { + changeFlashSettings(device: device, mode: .on) + capturePhotoAsyncronously(completionHandler: { (_) in }) + + } else if device.hasFlash == false && flashEnabled == true && currentCamera == .front { + flashView = UIView(frame: view.frame) + flashView?.alpha = 0.0 + flashView?.backgroundColor = UIColor.white + previewLayer.addSubview(flashView!) + + UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveEaseInOut, animations: { + self.flashView?.alpha = 1.0 + + }, completion: { (_) in + self.capturePhotoAsyncronously(completionHandler: { (success) in + UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveEaseInOut, animations: { + self.flashView?.alpha = 0.0 + }, completion: { (_) in + self.flashView?.removeFromSuperview() + }) + }) + }) + } else { + if device.isFlashActive == true { + changeFlashSettings(device: device, mode: .off) + } + capturePhotoAsyncronously(completionHandler: { (_) in }) + } + } + + /** + Begin recording video of current session + SwiftyCamViewControllerDelegate function SwiftyCamDidBeginRecordingVideo() will be called + */ + + public func startVideoRecording() { + guard let movieFileOutput = self.movieFileOutput else { + return + } + + if currentCamera == .rear && flashEnabled == true { + enableFlash() + } + + if currentCamera == .front && flashEnabled == true { + flashView = UIView(frame: view.frame) + flashView?.backgroundColor = UIColor.white + 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 { + if UIDevice.current.isMultitaskingSupported { + self.backgroundRecordingID = UIApplication.shared.beginBackgroundTask(expirationHandler: nil) + } + + // Update the orientation on the movie file output video connection before starting recording. + let movieFileOutputConnection = self.movieFileOutput?.connection(with: AVMediaType.video) + + //flip video output if front facing camera is selected + if self.currentCamera == .front { + movieFileOutputConnection?.isVideoMirrored = true + } + + movieFileOutputConnection?.videoOrientation = self.orientation.getVideoOrientation() ?? previewOrientation + + // Start recording to a temporary file. + let outputFileName = UUID().uuidString + let outputFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent((outputFileName as NSString).appendingPathExtension("mov")!) + movieFileOutput.startRecording(to: URL(fileURLWithPath: outputFilePath), recordingDelegate: self) + self.isVideoRecording = true + DispatchQueue.main.async { + self.cameraDelegate?.swiftyCam(self, didBeginRecordingVideo: self.currentCamera) + } + } + else { + movieFileOutput.stopRecording() + } + } + } + + /** + Stop video recording video of current session + SwiftyCamViewControllerDelegate function SwiftyCamDidFinishRecordingVideo() will be called + When video has finished processing, the URL to the video location will be returned by SwiftyCamDidFinishProcessingVideoAt(url:) + */ + + public func stopVideoRecording() { + if self.movieFileOutput?.isRecording == true { + self.isVideoRecording = false + movieFileOutput!.stopRecording() + disableFlash() + + if currentCamera == .front && flashEnabled == true && flashView != nil { + UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveEaseInOut, animations: { + self.flashView?.alpha = 0.0 + }, completion: { (_) in + self.flashView?.removeFromSuperview() + }) + } + DispatchQueue.main.async { + self.cameraDelegate?.swiftyCam(self, didFinishRecordingVideo: self.currentCamera) + } + } + } - sessionQueue.async { [unowned self] in - if !movieFileOutput.isRecording { - if UIDevice.current.isMultitaskingSupported { - self.backgroundRecordingID = UIApplication.shared.beginBackgroundTask(expirationHandler: nil) - } - - // Update the orientation on the movie file output video connection before starting recording. - let movieFileOutputConnection = self.movieFileOutput?.connection(with: AVMediaType.video) - - - //flip video output if front facing camera is selected - if self.currentCamera == .front { - movieFileOutputConnection?.isVideoMirrored = true - } - - movieFileOutputConnection?.videoOrientation = self.orientation.getVideoOrientation() ?? previewOrientation - - // Start recording to a temporary file. - let outputFileName = UUID().uuidString - let outputFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent((outputFileName as NSString).appendingPathExtension("mov")!) - movieFileOutput.startRecording(to: URL(fileURLWithPath: outputFilePath), recordingDelegate: self) - self.isVideoRecording = true - DispatchQueue.main.async { - self.cameraDelegate?.swiftyCam(self, didBeginRecordingVideo: self.currentCamera) - } - } - else { - movieFileOutput.stopRecording() - } - } - } - - /** - - Stop video recording video of current session - - SwiftyCamViewControllerDelegate function SwiftyCamDidFinishRecordingVideo() will be called - - When video has finished processing, the URL to the video location will be returned by SwiftyCamDidFinishProcessingVideoAt(url:) - - */ - - public func stopVideoRecording() { - if self.movieFileOutput?.isRecording == true { - self.isVideoRecording = false - movieFileOutput!.stopRecording() - disableFlash() - - if currentCamera == .front && flashEnabled == true && flashView != nil { - UIView.animate(withDuration: 0.1, delay: 0.0, options: .curveEaseInOut, animations: { - self.flashView?.alpha = 0.0 - }, completion: { (_) in - self.flashView?.removeFromSuperview() - }) - } - DispatchQueue.main.async { - self.cameraDelegate?.swiftyCam(self, didFinishRecordingVideo: self.currentCamera) - } - } - } - - /** - - Switch between front and rear camera - - SwiftyCamViewControllerDelegate function SwiftyCamDidSwitchCameras(camera: will be return the current camera selection - - */ - - - public func switchCamera() { - guard isVideoRecording != true else { - //TODO: Look into switching camera during video recording - print("[SwiftyCam]: Switching between cameras while recording video is not supported") - return - } - + /** + Switch between front and rear camera + SwiftyCamViewControllerDelegate function SwiftyCamDidSwitchCameras(camera: will be return the current camera selection + */ + + + public func switchCamera() { + guard isVideoRecording != true else { + //TODO: Look into switching camera during video recording + print("[SwiftyCam]: Switching between cameras while recording video is not supported") + return + } + guard session.isRunning == true else { return } - - switch currentCamera { - case .front: - currentCamera = .rear - case .rear: - currentCamera = .front - } - - session.stopRunning() - - sessionQueue.async { [unowned self] in - - // remove and re-add inputs and outputs - - for input in self.session.inputs { - self.session.removeInput(input ) - } - - self.addInputs() - DispatchQueue.main.async { - self.cameraDelegate?.swiftyCam(self, didSwitchCameras: self.currentCamera) - } - - self.session.startRunning() - } - - // If flash is enabled, disable it as the torch is needed for front facing camera - disableFlash() - } - - // MARK: Private Functions - - /// Configure session, add inputs and outputs - - fileprivate func configureSession() { - guard setupResult == .success else { - return - } - - // Set default camera - - currentCamera = defaultCamera - - // begin configuring session - - session.beginConfiguration() - configureVideoPreset() - addVideoInput() - addAudioInput() - configureVideoOutput() - configurePhotoOutput() - - session.commitConfiguration() - } - - /// Add inputs after changing camera() - - fileprivate func addInputs() { - session.beginConfiguration() - configureVideoPreset() - addVideoInput() - addAudioInput() - session.commitConfiguration() - } - - - // Front facing camera will always be set to VideoQuality.high - // If set video quality is not supported, videoQuality variable will be set to VideoQuality.high - /// Configure image quality preset - - fileprivate func configureVideoPreset() { - if currentCamera == .front { - session.sessionPreset = AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: .high)) - } else { - if session.canSetSessionPreset(AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: videoQuality))) { - session.sessionPreset = AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: videoQuality)) - } else { - session.sessionPreset = AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: .high)) - } - } - } - - /// Add Video Inputs - - fileprivate func addVideoInput() { - switch currentCamera { - case .front: - videoDevice = SwiftyCamViewController.deviceWithMediaType(AVMediaType.video.rawValue, preferringPosition: .front) - case .rear: - videoDevice = SwiftyCamViewController.deviceWithMediaType(AVMediaType.video.rawValue, preferringPosition: .back) - } - - if let device = videoDevice { - do { - try device.lockForConfiguration() - if device.isFocusModeSupported(.continuousAutoFocus) { - device.focusMode = .continuousAutoFocus - if device.isSmoothAutoFocusSupported { - device.isSmoothAutoFocusEnabled = true - } - } - - if device.isExposureModeSupported(.continuousAutoExposure) { - device.exposureMode = .continuousAutoExposure - } - - if device.isWhiteBalanceModeSupported(.continuousAutoWhiteBalance) { - device.whiteBalanceMode = .continuousAutoWhiteBalance - } - - if device.isLowLightBoostSupported && lowLightBoost == true { - device.automaticallyEnablesLowLightBoostWhenAvailable = true - } - - device.unlockForConfiguration() - } catch { - print("[SwiftyCam]: Error locking configuration") - } - } - - do { - if let videoDevice = videoDevice { - let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice) - if session.canAddInput(videoDeviceInput) { - session.addInput(videoDeviceInput) - self.videoDeviceInput = videoDeviceInput - } else { - print("[SwiftyCam]: Could not add video device input to the session") - print(session.canSetSessionPreset(AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: videoQuality)))) - setupResult = .configurationFailed - session.commitConfiguration() - return - } + + switch currentCamera { + case .front: + currentCamera = .rear + case .rear: + currentCamera = .front + } + + session.stopRunning() + + sessionQueue.async { [unowned self] in + + // remove and re-add inputs and outputs + for input in self.session.inputs { + self.session.removeInput(input) } - - } catch { - print("[SwiftyCam]: Could not create video device input: \(error)") - setupResult = .configurationFailed - return - } - } - - /// Add Audio Inputs - - fileprivate func addAudioInput() { - guard audioEnabled == true else { + + self.addInputs() + DispatchQueue.main.async { + self.cameraDelegate?.swiftyCam(self, didSwitchCameras: self.currentCamera) + } + + self.session.startRunning() + } + + // If flash is enabled, disable it as the torch is needed for front facing camera + disableFlash() + } + + // MARK: Private Functions + /// Configure session, add inputs and outputs + fileprivate func configureSession() { + guard setupResult == .success else { return } - do { - if let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio){ - let audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice) - if session.canAddInput(audioDeviceInput) { - session.addInput(audioDeviceInput) - } else { - print("[SwiftyCam]: Could not add audio device input to the session") + + // Set default camera + currentCamera = defaultCamera + + // begin configuring session + session.beginConfiguration() + configureVideoPreset() + addVideoInput() + addAudioInput() + configureVideoOutput() + configurePhotoOutput() + + session.commitConfiguration() + } + + /// Add inputs after changing camera() + fileprivate func addInputs() { + session.beginConfiguration() + configureVideoPreset() + addVideoInput() + addAudioInput() + session.commitConfiguration() + } + + + // Front facing camera will always be set to VideoQuality.high + // If set video quality is not supported, videoQuality variable will be set to VideoQuality.high + /// Configure image quality preset + fileprivate func configureVideoPreset() { + if currentCamera == .front { + session.sessionPreset = AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: .high)) + } else { + if session.canSetSessionPreset(AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: videoQuality))) { + session.sessionPreset = AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: videoQuality)) + } else { + session.sessionPreset = AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: .high)) + } + } + } + + /// Add Video Inputs + fileprivate func addVideoInput() { + switch currentCamera { + case .front: + videoDevice = SwiftyCamViewController.deviceWithMediaType(AVMediaType.video.rawValue, preferringPosition: .front) + case .rear: + videoDevice = SwiftyCamViewController.deviceWithMediaType(AVMediaType.video.rawValue, preferringPosition: .back) + } + + if let device = videoDevice { + do { + try device.lockForConfiguration() + if device.isFocusModeSupported(.continuousAutoFocus) { + device.focusMode = .continuousAutoFocus + if device.isSmoothAutoFocusSupported { + device.isSmoothAutoFocusEnabled = true + } + } + + if device.isExposureModeSupported(.continuousAutoExposure) { + device.exposureMode = .continuousAutoExposure + } + + if device.isWhiteBalanceModeSupported(.continuousAutoWhiteBalance) { + device.whiteBalanceMode = .continuousAutoWhiteBalance } + if device.isLowLightBoostSupported && lowLightBoost == true { + device.automaticallyEnablesLowLightBoostWhenAvailable = true + } + + device.unlockForConfiguration() + } catch { + print("[SwiftyCam]: Error locking configuration") + } + } + + do { + let videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice!) + + if session.canAddInput(videoDeviceInput) { + session.addInput(videoDeviceInput) + self.videoDeviceInput = videoDeviceInput } else { - print("[SwiftyCam]: Could not find an audio device") + print("[SwiftyCam]: Could not add video device input to the session") + print(session.canSetSessionPreset(AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: videoQuality)))) + setupResult = .configurationFailed + session.commitConfiguration() + return } + } catch { + print("[SwiftyCam]: Could not create video device input: \(error)") + setupResult = .configurationFailed + return + } + } + + /// Add Audio Inputs + fileprivate func addAudioInput() { + guard audioEnabled == true else { + return + } + do { + let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio) - } catch { - print("[SwiftyCam]: Could not create audio device input: \(error)") - } - } - - /// Configure Movie Output - - fileprivate func configureVideoOutput() { - let movieFileOutput = AVCaptureMovieFileOutput() - - if self.session.canAddOutput(movieFileOutput) { - self.session.addOutput(movieFileOutput) - if let connection = movieFileOutput.connection(with: AVMediaType.video) { - if connection.isVideoStabilizationSupported { - connection.preferredVideoStabilizationMode = .auto - } - } - self.movieFileOutput = movieFileOutput - } - } - - /// Configure Photo Output - - fileprivate func configurePhotoOutput() { - let photoFileOutput = AVCaptureStillImageOutput() - - if self.session.canAddOutput(photoFileOutput) { - photoFileOutput.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG] - self.session.addOutput(photoFileOutput) - self.photoFileOutput = photoFileOutput - } - } - - - /** - Returns a UIImage from Image Data. - - - Parameter imageData: Image Data returned from capturing photo from the capture session. - - - Returns: UIImage from the image data, adjusted for proper orientation. - */ - - fileprivate func processPhoto(_ imageData: Data) -> UIImage { - let dataProvider = CGDataProvider(data: imageData as CFData) - let cgImageRef = CGImage(jpegDataProviderSource: dataProvider!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent) - - // 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.orientation.getImageOrientation(forCamera: self.currentCamera)) - - return image - } - - fileprivate func capturePhotoAsyncronously(completionHandler: @escaping(Bool) -> ()) { + guard audioDevice != nil else { + print("[SwiftyCam]: Could not add audio device, Device not found.") + return + } + let audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice!) + + if session.canAddInput(audioDeviceInput) { + session.addInput(audioDeviceInput) + } + else { + print("[SwiftyCam]: Could not add audio device input to the session") + } + } + catch { + print("[SwiftyCam]: Could not create audio device input: \(error)") + } + } + + /// Configure Movie Output + fileprivate func configureVideoOutput() { + let movieFileOutput = AVCaptureMovieFileOutput() + + if self.session.canAddOutput(movieFileOutput) { + self.session.addOutput(movieFileOutput) + if let connection = movieFileOutput.connection(with: AVMediaType.video) { + if connection.isVideoStabilizationSupported { + connection.preferredVideoStabilizationMode = .auto + } + } + self.movieFileOutput = movieFileOutput + } + } + + /// Configure Photo Output + fileprivate func configurePhotoOutput() { + let photoFileOutput = AVCaptureStillImageOutput() + + if self.session.canAddOutput(photoFileOutput) { + photoFileOutput.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG] + self.session.addOutput(photoFileOutput) + self.photoFileOutput = photoFileOutput + } + } + + fileprivate func processPhoto(_ imageData: Data) -> UIImage { + let dataProvider = CGDataProvider(data: imageData as CFData) + let cgImageRef = CGImage(jpegDataProviderSource: dataProvider!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent) + + // 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.orientation.getImageOrientation(forCamera: self.currentCamera)) + + return image + } + + fileprivate func capturePhotoAsyncronously(completionHandler: @escaping(Bool) -> ()) { + guard sessionRunning == true else { print("[SwiftyCam]: Cannot take photo. Capture session is not running") return } - if let videoConnection = photoFileOutput?.connection(with: AVMediaType.video) { - - photoFileOutput?.captureStillImageAsynchronously(from: videoConnection, completionHandler: {(sampleBuffer, error) in - if (sampleBuffer != nil) { - let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer!) - let image = self.processPhoto(imageData!) - - // Call delegate and return new image - DispatchQueue.main.async { - self.cameraDelegate?.swiftyCam(self, didTake: image) - } - completionHandler(true) - } else { - completionHandler(false) - } - }) - } else { - completionHandler(false) - } - } - - /// Handle Denied App Privacy Settings - - fileprivate func promptToAppSettings() { - // prompt User with UIAlertView - - DispatchQueue.main.async(execute: { [unowned self] in - let message = NSLocalizedString("AVCam doesn't have permission to use the camera, please change privacy settings", comment: "Alert message when the user has denied access to the camera") - let alertController = UIAlertController(title: "AVCam", message: message, preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil)) - alertController.addAction(UIAlertAction(title: NSLocalizedString("Settings", comment: "Alert button to open Settings"), style: .default, handler: { action in - if #available(iOS 10.0, *) { - UIApplication.shared.openURL(URL(string: UIApplicationOpenSettingsURLString)!) - } else { - if let appSettings = URL(string: UIApplicationOpenSettingsURLString) { - UIApplication.shared.openURL(appSettings) - } - } - })) - self.present(alertController, animated: true, completion: nil) - }) - } - - /** - Returns an AVCapturePreset from VideoQuality Enumeration - - - Parameter quality: ViewQuality enum - - - Returns: String representing a AVCapturePreset - */ - - fileprivate func videoInputPresetFromVideoQuality(quality: VideoQuality) -> String { - switch quality { - case .high: return AVCaptureSession.Preset.high.rawValue - case .medium: return AVCaptureSession.Preset.medium.rawValue - case .low: return AVCaptureSession.Preset.low.rawValue - case .resolution352x288: return AVCaptureSession.Preset.cif352x288.rawValue - case .resolution640x480: return AVCaptureSession.Preset.vga640x480.rawValue - case .resolution1280x720: return AVCaptureSession.Preset.hd1280x720.rawValue - case .resolution1920x1080: return AVCaptureSession.Preset.hd1920x1080.rawValue - case .iframe960x540: return AVCaptureSession.Preset.iFrame960x540.rawValue - case .iframe1280x720: return AVCaptureSession.Preset.iFrame1280x720.rawValue - case .resolution3840x2160: - if #available(iOS 9.0, *) { - return AVCaptureSession.Preset.hd4K3840x2160.rawValue - } - else { - print("[SwiftyCam]: Resolution 3840x2160 not supported") - return AVCaptureSession.Preset.high.rawValue - } - } - } - - /// Get Devices - - fileprivate class func deviceWithMediaType(_ mediaType: String, preferringPosition position: AVCaptureDevice.Position) -> AVCaptureDevice? { - 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 - - fileprivate func changeFlashSettings(device: AVCaptureDevice, mode: AVCaptureDevice.FlashMode) { - do { - try device.lockForConfiguration() - device.flashMode = mode - device.unlockForConfiguration() - } catch { - print("[SwiftyCam]: \(error)") - } - } - - /// Enable flash - - fileprivate func enableFlash() { - if self.isCameraTorchOn == false { - toggleFlash() - } - } - - /// Disable flash - - fileprivate func disableFlash() { - if self.isCameraTorchOn == true { - toggleFlash() - } - } - - /// Toggles between enabling and disabling flash - - fileprivate func toggleFlash() { - guard self.currentCamera == .rear else { - // Flash is not supported for front facing camera - return - } - - let device = AVCaptureDevice.default(for: AVMediaType.video) - // Check if device has a flash - if (device?.hasTorch)! { - do { - try device?.lockForConfiguration() - if (device?.torchMode == AVCaptureDevice.TorchMode.on) { - device?.torchMode = AVCaptureDevice.TorchMode.off - self.isCameraTorchOn = false - } else { - do { - try device?.setTorchModeOn(level: 1.0) - self.isCameraTorchOn = true - } catch { - print("[SwiftyCam]: \(error)") - } - } - device?.unlockForConfiguration() - } catch { - print("[SwiftyCam]: \(error)") - } - } - } - - /// Sets whether SwiftyCam should enable background audio from other applications or sources - - fileprivate func setBackgroundAudioPreference() { - guard allowBackgroundAudio == true else { - return - } + if let videoConnection = photoFileOutput?.connection(with: AVMediaType.video) { + + photoFileOutput?.captureStillImageAsynchronously(from: videoConnection, completionHandler: {(sampleBuffer, error) in + if (sampleBuffer != nil) { + let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer!) + let image = self.processPhoto(imageData!) + + // Call delegate and return new image + DispatchQueue.main.async { + self.cameraDelegate?.swiftyCam(self, didTake: image) + } + completionHandler(true) + } else { + completionHandler(false) + } + }) + } else { + completionHandler(false) + } + } + - guard audioEnabled == true else { + fileprivate func promptToAppSettings() { + if usePermissionAlert { + // prompt User with UIAlertView + + DispatchQueue.main.async(execute: { [unowned self] in + let message = NSLocalizedString("AVCam doesn't have permission to use the camera, please change privacy settings", comment: "Alert message when the user has denied access to the camera") + let alertController = UIAlertController(title: "AVCam", message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Alert OK button"), style: .cancel, handler: nil)) + alertController.addAction(UIAlertAction(title: NSLocalizedString("Settings", comment: "Alert button to open Settings"), style: .default, handler: { action in + self.openSettings() + })) + self.present(alertController, animated: true, completion: nil) + }) + } else { + DispatchQueue.main.async { + self.permissionErrorLabel.isHidden = false + self.permissionErrorButton.isHidden = false + } + } + + } + + @objc func openSettings() { + if #available(iOS 10.0, *) { + UIApplication.shared.openURL(URL(string: UIApplication.openSettingsURLString)!) + } else { + if let appSettings = URL(string: UIApplication.openSettingsURLString) { + UIApplication.shared.openURL(appSettings) + } + } + } + + /** + Returns an AVCapturePreset from VideoQuality Enumeration + - Parameter quality: ViewQuality enum + - Returns: String representing a AVCapturePreset + */ + + fileprivate func videoInputPresetFromVideoQuality(quality: VideoQuality) -> String { + switch quality { + case .high: return AVCaptureSession.Preset.high.rawValue + case .medium: return AVCaptureSession.Preset.medium.rawValue + case .low: return AVCaptureSession.Preset.low.rawValue + case .resolution352x288: return AVCaptureSession.Preset.cif352x288.rawValue + case .resolution640x480: return AVCaptureSession.Preset.vga640x480.rawValue + case .resolution1280x720: return AVCaptureSession.Preset.hd1280x720.rawValue + case .resolution1920x1080: return AVCaptureSession.Preset.hd1920x1080.rawValue + case .iframe960x540: return AVCaptureSession.Preset.iFrame960x540.rawValue + case .iframe1280x720: return AVCaptureSession.Preset.iFrame1280x720.rawValue + case .resolution3840x2160: + if #available(iOS 9.0, *) { + return AVCaptureSession.Preset.hd4K3840x2160.rawValue + } + else { + print("[SwiftyCam]: Resolution 3840x2160 not supported") + return AVCaptureSession.Preset.high.rawValue + } + } + } + + fileprivate class func deviceWithMediaType(_ mediaType: String, preferringPosition position: AVCaptureDevice.Position) -> AVCaptureDevice? { + 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 { + if device.position == position { + break + } else { + avDeviceNum += 1 + } + } + return avDevice[avDeviceNum] + } + return nil + } + + /// Enable or disable flash for photo + fileprivate func changeFlashSettings(device: AVCaptureDevice, mode: AVCaptureDevice.FlashMode) { + do { + try device.lockForConfiguration() + device.flashMode = mode + device.unlockForConfiguration() + } catch { + print("[SwiftyCam]: \(error)") + } + } + + /// Enable flash + fileprivate func enableFlash() { + if self.isCameraTorchOn == false { + toggleFlash() + } + } + + /// Disable flash + fileprivate func disableFlash() { + if self.isCameraTorchOn == true { + toggleFlash() + } + } + + /// Toggles between enabling and disabling flash + fileprivate func toggleFlash() { + guard self.currentCamera == .rear else { + // Flash is not supported for front facing camera return } - - do{ - if #available(iOS 10.0, *) { - try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, - with: [.mixWithOthers, .allowBluetooth, .allowAirPlay, .allowBluetoothA2DP]) - } else { - try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, - with: [.mixWithOthers, .allowBluetooth]) + + let device = AVCaptureDevice.default(for: AVMediaType.video) + // Check if device has a flash + if (device?.hasTorch)! { + do { + try device?.lockForConfiguration() + if (device?.torchMode == AVCaptureDevice.TorchMode.on) { + device?.torchMode = AVCaptureDevice.TorchMode.off + self.isCameraTorchOn = false + } else { + do { + try device?.setTorchModeOn(level: 1.0) + self.isCameraTorchOn = true + } catch { + print("[SwiftyCam]: \(error)") + } + } + device?.unlockForConfiguration() + } catch { + print("[SwiftyCam]: \(error)") } - session.automaticallyConfiguresApplicationAudioSession = false - } - catch { - print("[SwiftyCam]: Failed to set background audio preference") - - } - } - - /// Called when Notification Center registers session starts running - - @objc private func captureSessionDidStartRunning() { - sessionRunning = true - DispatchQueue.main.async { - self.cameraDelegate?.swiftyCamSessionDidStartRunning(self) } } - - /// Called when Notification Center registers session stops running - - @objc private func captureSessionDidStopRunning() { - sessionRunning = false - DispatchQueue.main.async { - self.cameraDelegate?.swiftyCamSessionDidStopRunning(self) + + /// Sets whether SwiftyCam should enable background audio from other applications or sources + fileprivate func setBackgroundAudioPreference() { + guard allowBackgroundAudio == true else { + return + } + + guard audioEnabled == true else { + return + } + + do{ + try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, options: [ + .duckOthers, + .defaultToSpeaker + ]) + session.automaticallyConfiguresApplicationAudioSession = false + } + catch { + print("[SwiftyCam]: Failed to set background audio preference") + } } -} - -extension SwiftyCamViewController : SwiftyCamButtonDelegate { - - /// Sets the maximum duration of the SwiftyCamButton - - public func setMaxiumVideoDuration() -> Double { - return maximumVideoDuration - } - - /// Set UITapGesture to take photo - - public func buttonWasTapped() { - takePhoto() - } - - /// Set UILongPressGesture start to begin video - - public func buttonDidBeginLongPress() { - startVideoRecording() - } - - /// Set UILongPressGesture begin to begin end video - - public func buttonDidEndLongPress() { - stopVideoRecording() - } - - /// Called if maximum duration is reached - - public func longPressDidReachMaximumDuration() { - stopVideoRecording() - } } // MARK: 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?) { if let currentBackgroundRecordingID = backgroundRecordingID { - backgroundRecordingID = UIBackgroundTaskInvalid + backgroundRecordingID = UIBackgroundTaskIdentifier.invalid - if currentBackgroundRecordingID != UIBackgroundTaskInvalid { + if currentBackgroundRecordingID != UIBackgroundTaskIdentifier.invalid { UIApplication.shared.endBackgroundTask(currentBackgroundRecordingID) } } - - if let currentError = error { - print("[SwiftyCam]: Movie file finishing error: \(currentError)") + if error != nil { + print("[SwiftyCam]: Movie file finishing error: \(error)") DispatchQueue.main.async { - self.cameraDelegate?.swiftyCam(self, didFailToRecordVideo: currentError) + self.cameraDelegate?.swiftyCam(self, didFailToRecordVideo: error!) } } else { //Call delegate function with the URL of the outputfile @@ -1083,78 +1108,78 @@ extension SwiftyCamViewController : AVCaptureFileOutputRecordingDelegate { extension SwiftyCamViewController { - /// Handle pinch gesture + /// Handle pinch gesture - @objc fileprivate func zoomGesture(pinch: UIPinchGestureRecognizer) { - guard pinchToZoom == true && self.currentCamera == .rear else { - //ignore pinch - return - } - do { - let captureDevice = AVCaptureDevice.devices().first - try captureDevice?.lockForConfiguration() + @objc fileprivate func zoomGesture(pinch: UIPinchGestureRecognizer) { + guard pinchToZoom == true && self.currentCamera == .rear else { + //ignore pinch + return + } + do { + let captureDevice = AVCaptureDevice.devices().first as? AVCaptureDevice + try captureDevice?.lockForConfiguration() - zoomScale = min(maxZoomScale, max(1.0, min(beginZoomScale * pinch.scale, captureDevice!.activeFormat.videoMaxZoomFactor))) + zoomScale = min(maxZoomScale, max(1.0, min(beginZoomScale * pinch.scale, captureDevice!.activeFormat.videoMaxZoomFactor))) - captureDevice?.videoZoomFactor = zoomScale + captureDevice?.videoZoomFactor = zoomScale - // Call Delegate function with current zoom scale - DispatchQueue.main.async { - self.cameraDelegate?.swiftyCam(self, didChangeZoomLevel: self.zoomScale) - } + // Call Delegate function with current zoom scale + DispatchQueue.main.async { + self.cameraDelegate?.swiftyCam(self, didChangeZoomLevel: self.zoomScale) + } - captureDevice?.unlockForConfiguration() + captureDevice?.unlockForConfiguration() - } catch { - print("[SwiftyCam]: Error locking configuration") - } - } + } catch { + print("[SwiftyCam]: Error locking configuration") + } + } - /// Handle single tap gesture - - @objc fileprivate func singleTapGesture(tap: UITapGestureRecognizer) { - guard tapToFocus == true else { - // Ignore taps - return - } - - let screenSize = previewLayer!.bounds.size - let tapPoint = tap.location(in: previewLayer!) - let x = tapPoint.y / screenSize.height - let y = 1.0 - tapPoint.x / screenSize.width - let focusPoint = CGPoint(x: x, y: y) - - if let device = videoDevice { - do { - try device.lockForConfiguration() - - if device.isFocusPointOfInterestSupported == true { - device.focusPointOfInterest = focusPoint - device.focusMode = .autoFocus - } - device.exposurePointOfInterest = focusPoint - device.exposureMode = AVCaptureDevice.ExposureMode.continuousAutoExposure - device.unlockForConfiguration() - //Call delegate function and pass in the location of the touch - - DispatchQueue.main.async { - self.cameraDelegate?.swiftyCam(self, didFocusAtPoint: tapPoint) - } - } - catch { - // just ignore - } - } - } + /// Handle single tap gesture - /// Handle double tap gesture + @objc fileprivate func singleTapGesture(tap: UITapGestureRecognizer) { + guard tapToFocus == true else { + // Ignore taps + return + } - @objc fileprivate func doubleTapGesture(tap: UITapGestureRecognizer) { - guard doubleTapCameraSwitch == true else { - return - } - switchCamera() - } + let screenSize = previewLayer!.bounds.size + let tapPoint = tap.location(in: previewLayer!) + let x = tapPoint.y / screenSize.height + let y = 1.0 - tapPoint.x / screenSize.width + let focusPoint = CGPoint(x: x, y: y) + + if let device = videoDevice { + do { + try device.lockForConfiguration() + + if device.isFocusPointOfInterestSupported == true { + device.focusPointOfInterest = focusPoint + device.focusMode = .autoFocus + } + device.exposurePointOfInterest = focusPoint + device.exposureMode = AVCaptureDevice.ExposureMode.continuousAutoExposure + device.unlockForConfiguration() + //Call delegate function and pass in the location of the touch + + DispatchQueue.main.async { + self.cameraDelegate?.swiftyCam(self, didFocusAtPoint: tapPoint) + } + } + catch { + // just ignore + } + } + } + + /// Handle double tap gesture + + @objc fileprivate func doubleTapGesture(tap: UITapGestureRecognizer) { + guard doubleTapCameraSwitch == true else { + return + } + switchCamera() + } @objc private func panGesture(pan: UIPanGestureRecognizer) { @@ -1198,32 +1223,32 @@ extension SwiftyCamViewController { } } - /** - Add pinch gesture recognizer and double tap gesture recognizer to currentView + /** + Add pinch gesture recognizer and double tap gesture recognizer to currentView - - Parameter view: View to add gesture recognzier + - Parameter view: View to add gesture recognzier - */ + */ - fileprivate func addGestureRecognizers() { - pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(zoomGesture(pinch:))) - pinchGesture.delegate = self - previewLayer.addGestureRecognizer(pinchGesture) + fileprivate func addGestureRecognizers() { + pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(zoomGesture(pinch:))) + pinchGesture.delegate = self + previewLayer.addGestureRecognizer(pinchGesture) - let singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(singleTapGesture(tap:))) - singleTapGesture.numberOfTapsRequired = 1 - singleTapGesture.delegate = self - previewLayer.addGestureRecognizer(singleTapGesture) + let singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(singleTapGesture(tap:))) + singleTapGesture.numberOfTapsRequired = 1 + singleTapGesture.delegate = self + previewLayer.addGestureRecognizer(singleTapGesture) - let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(doubleTapGesture(tap:))) - doubleTapGesture.numberOfTapsRequired = 2 - doubleTapGesture.delegate = self - previewLayer.addGestureRecognizer(doubleTapGesture) + let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(doubleTapGesture(tap:))) + doubleTapGesture.numberOfTapsRequired = 2 + doubleTapGesture.delegate = self + previewLayer.addGestureRecognizer(doubleTapGesture) panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGesture(pan:))) panGesture.delegate = self previewLayer.addGestureRecognizer(panGesture) - } + } } @@ -1231,12 +1256,12 @@ extension SwiftyCamViewController { extension SwiftyCamViewController : UIGestureRecognizerDelegate { - /// Set beginZoomScale when pinch begins + /// Set beginZoomScale when pinch begins - public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - if gestureRecognizer.isKind(of: UIPinchGestureRecognizer.self) { - beginZoomScale = zoomScale; - } - return true - } + public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer.isKind(of: UIPinchGestureRecognizer.self) { + beginZoomScale = zoomScale; + } + return true + } } diff --git a/SwiftyCam.podspec b/SwiftyCam.podspec index 1fcb90d..9fa7e9c 100644 --- a/SwiftyCam.podspec +++ b/SwiftyCam.podspec @@ -10,7 +10,7 @@ Pod::Spec.new do |s| s.name = 'SwiftyCam' s.version = '2.7.0' s.summary = 'A Simple, Snapchat inspired camera Framework written in Swift' - s.ios.deployment_target = '8.0' + s.ios.deployment_target = '10.0' s.description = <<-DESC A drop in Camera View Controller for capturing photos and videos from one AVSession. Written in Swift. @@ -23,7 +23,7 @@ A drop in Camera View Controller for capturing photos and videos from one AVSess s.source = { :git => 'https://github.com/Awalz/SwiftyCam.git', :tag => s.version.to_s } # s.social_media_url = 'https://twitter.com/' - s.ios.deployment_target = '8.0' + s.ios.deployment_target = '10.0' s.source_files = 'Source/**/*' end