diff --git a/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/project.pbxproj b/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/project.pbxproj index 3d8dcef..557ee4e 100644 --- a/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/project.pbxproj +++ b/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 16298B561E2703DC0056D413 /* SwiftyRecordButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16298B551E2703DC0056D413 /* SwiftyRecordButton.swift */; }; - 165CDA841E68802500FD0343 /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 165CDA831E68802500FD0343 /* UIImage+Extension.swift */; }; 1675A9761E00A68300B80903 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1675A9751E00A68300B80903 /* AppDelegate.swift */; }; 1675A9781E00A68300B80903 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1675A9771E00A68300B80903 /* ViewController.swift */; }; 1675A97B1E00A68300B80903 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1675A9791E00A68300B80903 /* Main.storyboard */; }; @@ -24,7 +23,6 @@ /* Begin PBXFileReference section */ 16298B551E2703DC0056D413 /* SwiftyRecordButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyRecordButton.swift; sourceTree = ""; }; - 165CDA831E68802500FD0343 /* UIImage+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; 1675A9721E00A68300B80903 /* DemoSwiftyCam.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DemoSwiftyCam.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1675A9751E00A68300B80903 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 1675A9771E00A68300B80903 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -91,7 +89,6 @@ 1675A98A1E00A74A00B80903 /* SwiftyCamButton.swift */, 1675A98B1E00A74A00B80903 /* SwiftyCamViewController.swift */, 1675A98C1E00A74A00B80903 /* SwiftyCamViewControllerDelegate.swift */, - 165CDA831E68802500FD0343 /* UIImage+Extension.swift */, ); name = Source; sourceTree = ""; @@ -128,7 +125,7 @@ TargetAttributes = { 1675A9711E00A68300B80903 = { CreatedOnToolsVersion = 8.1; - DevelopmentTeam = LW28KCU8N5; + DevelopmentTeam = 427VB57543; ProvisioningStyle = Automatic; }; }; @@ -176,7 +173,6 @@ 1675A98F1E00A74A00B80903 /* SwiftyCamViewController.swift in Sources */, 168505EA1E288D80005B4537 /* PhotoViewController.swift in Sources */, 16298B561E2703DC0056D413 /* SwiftyRecordButton.swift in Sources */, - 165CDA841E68802500FD0343 /* UIImage+Extension.swift in Sources */, 1675A98D1E00A74A00B80903 /* PreviewView.swift in Sources */, 1675A98E1E00A74A00B80903 /* SwiftyCamButton.swift in Sources */, ); @@ -301,9 +297,9 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = LW28KCU8N5; + DEVELOPMENT_TEAM = 427VB57543; INFOPLIST_FILE = DemoSwiftyCam/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.Cappsule.DemoSwiftyCam; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -315,9 +311,9 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = LW28KCU8N5; + DEVELOPMENT_TEAM = 427VB57543; INFOPLIST_FILE = DemoSwiftyCam/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.Cappsule.DemoSwiftyCam; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/DemoSwiftyCam/DemoSwiftyCam/Info.plist b/DemoSwiftyCam/DemoSwiftyCam/Info.plist index c2cc98e..da4dd18 100644 --- a/DemoSwiftyCam/DemoSwiftyCam/Info.plist +++ b/DemoSwiftyCam/DemoSwiftyCam/Info.plist @@ -2,10 +2,6 @@ - NSMicrophoneUsageDescription - To record audio with videos - NSCameraUsageDescription - To take photos and videos CFBundleDevelopmentRegion en CFBundleExecutable @@ -24,6 +20,10 @@ 1 LSRequiresIPhoneOS + NSCameraUsageDescription + To take photos and videos + NSMicrophoneUsageDescription + To record audio with videos UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -35,8 +35,6 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight diff --git a/DemoSwiftyCam/DemoSwiftyCam/PhotoViewController.swift b/DemoSwiftyCam/DemoSwiftyCam/PhotoViewController.swift index edb62b4..89dc74a 100644 --- a/DemoSwiftyCam/DemoSwiftyCam/PhotoViewController.swift +++ b/DemoSwiftyCam/DemoSwiftyCam/PhotoViewController.swift @@ -1,50 +1,51 @@ /*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. */ + +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 UIKit class PhotoViewController: UIViewController { - - override var prefersStatusBarHidden: Bool { - return true - } - - private var backgroundImage: UIImage - - init(image: UIImage) { - self.backgroundImage = image - super.init(nibName: nil, bundle: nil) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - override func viewDidLoad() { - super.viewDidLoad() - self.view.backgroundColor = UIColor.gray - let backgroundImageView = UIImageView(frame: view.frame) - 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.addTarget(self, action: #selector(cancel), for: .touchUpInside) - view.addSubview(cancelButton) - } - - func cancel() { - dismiss(animated: true, completion: nil) - } + override var prefersStatusBarHidden: Bool { + return true + } + + private var backgroundImage: UIImage + + init(image: UIImage) { + self.backgroundImage = image + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = UIColor.gray + let backgroundImageView = UIImageView(frame: view.frame) + backgroundImageView.contentMode = UIViewContentMode.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.addTarget(self, action: #selector(cancel), for: .touchUpInside) + view.addSubview(cancelButton) + } + + func cancel() { + dismiss(animated: true, completion: nil) + } } diff --git a/DemoSwiftyCam/DemoSwiftyCam/UIImage+Extension.swift b/DemoSwiftyCam/DemoSwiftyCam/UIImage+Extension.swift deleted file mode 100644 index f963353..0000000 --- a/DemoSwiftyCam/DemoSwiftyCam/UIImage+Extension.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// UIImage+Extension.swift -// DemoSwiftyCam -// -// Created by Andrew on 2017-03-02. -// Copyright © 2017 Cappsule. All rights reserved. -// - -import UIKit - -extension UIImage { - func fixOrientation() -> UIImage? { - - guard let cgImage = self.cgImage else { - return nil - } - - if self.imageOrientation == UIImageOrientation.up { - return self - } - - let width = self.size.width - let height = self.size.height - - var transform = CGAffineTransform.identity - - switch self.imageOrientation { - case .down, .downMirrored: - transform = transform.translatedBy(x: width, y: height) - transform = transform.rotated(by: CGFloat.pi) - - case .left, .leftMirrored: - transform = transform.translatedBy(x: width, y: 0) - transform = transform.rotated(by: 0.5*CGFloat.pi) - - case .right, .rightMirrored: - transform = transform.translatedBy(x: 0, y: height) - transform = transform.rotated(by: -0.5*CGFloat.pi) - - case .up, .upMirrored: - break - } - - switch self.imageOrientation { - case .upMirrored, .downMirrored: - transform = transform.translatedBy(x: width, y: 0) - transform = transform.scaledBy(x: -1, y: 1) - - case .leftMirrored, .rightMirrored: - transform = transform.translatedBy(x: height, y: 0) - transform = transform.scaledBy(x: -1, y: 1) - - default: - break; - } - - // Now we draw the underlying CGImage into a new context, applying the transform - // calculated above. - guard let colorSpace = cgImage.colorSpace else { - return nil - } - - guard let context = CGContext( - data: nil, - width: Int(width), - height: Int(height), - bitsPerComponent: cgImage.bitsPerComponent, - bytesPerRow: 0, - space: colorSpace, - bitmapInfo: UInt32(cgImage.bitmapInfo.rawValue) - ) else { - return nil - } - - context.concatenate(transform); - - switch self.imageOrientation { - - case .left, .leftMirrored, .right, .rightMirrored: - // Grr... - context.draw(cgImage, in: CGRect(x: 0, y: 0, width: height, height: width)) - - default: - context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) - } - - // And now we just create a new UIImage from the drawing context - guard let newCGImg = context.makeImage() else { - return nil - } - - let img = UIImage(cgImage: newCGImg) - - return img; - } -} diff --git a/DemoSwiftyCam/DemoSwiftyCam/ViewController.swift b/DemoSwiftyCam/DemoSwiftyCam/ViewController.swift index 6a24db8..a71ac3b 100644 --- a/DemoSwiftyCam/DemoSwiftyCam/ViewController.swift +++ b/DemoSwiftyCam/DemoSwiftyCam/ViewController.swift @@ -1,127 +1,128 @@ /*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. */ + +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 UIKit class ViewController: SwiftyCamViewController, SwiftyCamViewControllerDelegate { - - var flipCameraButton: UIButton! - var flashButton: UIButton! - var captureButton: SwiftyRecordButton! - - override func viewDidLoad() { - super.viewDidLoad() - cameraDelegate = self - maximumVideoDuration = 10.0 - addButtons() - } - - override var prefersStatusBarHidden: Bool { - return true - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - } - - func swiftyCam(_ swiftyCam: SwiftyCamViewController, didTake photo: UIImage) { - let newVC = PhotoViewController(image: photo) - self.present(newVC, animated: true, completion: nil) - } - - func swiftyCam(_ swiftyCam: SwiftyCamViewController, didBeginRecordingVideo camera: SwiftyCamViewController.CameraSelection) { - print("Did Begin Recording") - captureButton.growButton() - UIView.animate(withDuration: 0.25, animations: { - self.flashButton.alpha = 0.0 - self.flipCameraButton.alpha = 0.0 - }) - } - - func swiftyCam(_ swiftyCam: SwiftyCamViewController, didFinishRecordingVideo camera: SwiftyCamViewController.CameraSelection) { - print("Did finish Recording") - captureButton.shrinkButton() - UIView.animate(withDuration: 0.25, animations: { - self.flashButton.alpha = 1.0 - self.flipCameraButton.alpha = 1.0 - }) - } - - func swiftyCam(_ swiftyCam: SwiftyCamViewController, didFinishProcessVideoAt url: URL) { - let newVC = VideoViewController(videoURL: url) - self.present(newVC, animated: true, completion: nil) - } - - func swiftyCam(_ swiftyCam: SwiftyCamViewController, didFocusAtPoint point: CGPoint) { - let focusView = UIImageView(image: #imageLiteral(resourceName: "focus")) - focusView.center = point - focusView.alpha = 0.0 - view.addSubview(focusView) - - UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: { - focusView.alpha = 1.0 - focusView.transform = CGAffineTransform(scaleX: 1.25, y: 1.25) - }, completion: { (success) in - UIView.animate(withDuration: 0.15, delay: 0.5, options: .curveEaseInOut, animations: { - focusView.alpha = 0.0 - focusView.transform = CGAffineTransform(translationX: 0.6, y: 0.6) - }, completion: { (success) in - focusView.removeFromSuperview() - }) - }) - } - - func swiftyCam(_ swiftyCam: SwiftyCamViewController, didChangeZoomLevel zoom: CGFloat) { - print(zoom) - } - - func swiftyCam(_ swiftyCam: SwiftyCamViewController, didSwitchCameras camera: SwiftyCamViewController.CameraSelection) { - print(camera) - } - - @objc private func cameraSwitchAction(_ sender: Any) { - switchCamera() - } - - @objc private func toggleFlashAction(_ sender: Any) { - flashEnabled = !flashEnabled - - if flashEnabled == true { - flashButton.setImage(#imageLiteral(resourceName: "flash"), for: UIControlState()) - } else { - flashButton.setImage(#imageLiteral(resourceName: "flashOutline"), for: UIControlState()) - } - } - - private func addButtons() { - captureButton = SwiftyRecordButton(frame: CGRect(x: view.frame.midX - 37.5, y: view.frame.height - 100.0, width: 75.0, height: 75.0)) - self.view.addSubview(captureButton) - captureButton.delegate = self - - flipCameraButton = UIButton(frame: CGRect(x: (((view.frame.width / 2 - 37.5) / 2) - 15.0), y: view.frame.height - 74.0, width: 30.0, height: 23.0)) - flipCameraButton.setImage(#imageLiteral(resourceName: "flipCamera"), for: UIControlState()) - flipCameraButton.addTarget(self, action: #selector(cameraSwitchAction(_:)), for: .touchUpInside) - self.view.addSubview(flipCameraButton) - - let test = CGFloat((view.frame.width - (view.frame.width / 2 + 37.5)) + ((view.frame.width / 2) - 37.5) - 9.0) - - flashButton = UIButton(frame: CGRect(x: test, y: view.frame.height - 77.5, width: 18.0, height: 30.0)) - flashButton.setImage(#imageLiteral(resourceName: "flashOutline"), for: UIControlState()) - flashButton.addTarget(self, action: #selector(toggleFlashAction(_:)), for: .touchUpInside) - self.view.addSubview(flashButton) - } + + var flipCameraButton: UIButton! + var flashButton: UIButton! + var captureButton: SwiftyRecordButton! + + override func viewDidLoad() { + super.viewDidLoad() + cameraDelegate = self + maximumVideoDuration = 10.0 + shouldUseDeviceOrientation = true + addButtons() + } + + override var prefersStatusBarHidden: Bool { + return true + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } + + func swiftyCam(_ swiftyCam: SwiftyCamViewController, didTake photo: UIImage) { + let newVC = PhotoViewController(image: photo) + self.present(newVC, animated: true, completion: nil) + } + + func swiftyCam(_ swiftyCam: SwiftyCamViewController, didBeginRecordingVideo camera: SwiftyCamViewController.CameraSelection) { + print("Did Begin Recording") + captureButton.growButton() + UIView.animate(withDuration: 0.25, animations: { + self.flashButton.alpha = 0.0 + self.flipCameraButton.alpha = 0.0 + }) + } + + func swiftyCam(_ swiftyCam: SwiftyCamViewController, didFinishRecordingVideo camera: SwiftyCamViewController.CameraSelection) { + print("Did finish Recording") + captureButton.shrinkButton() + UIView.animate(withDuration: 0.25, animations: { + self.flashButton.alpha = 1.0 + self.flipCameraButton.alpha = 1.0 + }) + } + + func swiftyCam(_ swiftyCam: SwiftyCamViewController, didFinishProcessVideoAt url: URL) { + let newVC = VideoViewController(videoURL: url) + self.present(newVC, animated: true, completion: nil) + } + + func swiftyCam(_ swiftyCam: SwiftyCamViewController, didFocusAtPoint point: CGPoint) { + let focusView = UIImageView(image: #imageLiteral(resourceName: "focus")) + focusView.center = point + focusView.alpha = 0.0 + view.addSubview(focusView) + + UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: { + focusView.alpha = 1.0 + focusView.transform = CGAffineTransform(scaleX: 1.25, y: 1.25) + }, completion: { (success) in + UIView.animate(withDuration: 0.15, delay: 0.5, options: .curveEaseInOut, animations: { + focusView.alpha = 0.0 + focusView.transform = CGAffineTransform(translationX: 0.6, y: 0.6) + }, completion: { (success) in + focusView.removeFromSuperview() + }) + }) + } + + func swiftyCam(_ swiftyCam: SwiftyCamViewController, didChangeZoomLevel zoom: CGFloat) { + print(zoom) + } + + func swiftyCam(_ swiftyCam: SwiftyCamViewController, didSwitchCameras camera: SwiftyCamViewController.CameraSelection) { + print(camera) + } + + @objc private func cameraSwitchAction(_ sender: Any) { + switchCamera() + } + + @objc private func toggleFlashAction(_ sender: Any) { + flashEnabled = !flashEnabled + + if flashEnabled == true { + flashButton.setImage(#imageLiteral(resourceName: "flash"), for: UIControlState()) + } else { + flashButton.setImage(#imageLiteral(resourceName: "flashOutline"), for: UIControlState()) + } + } + + private func addButtons() { + captureButton = SwiftyRecordButton(frame: CGRect(x: view.frame.midX - 37.5, y: view.frame.height - 100.0, width: 75.0, height: 75.0)) + self.view.addSubview(captureButton) + captureButton.delegate = self + + flipCameraButton = UIButton(frame: CGRect(x: (((view.frame.width / 2 - 37.5) / 2) - 15.0), y: view.frame.height - 74.0, width: 30.0, height: 23.0)) + flipCameraButton.setImage(#imageLiteral(resourceName: "flipCamera"), for: UIControlState()) + flipCameraButton.addTarget(self, action: #selector(cameraSwitchAction(_:)), for: .touchUpInside) + self.view.addSubview(flipCameraButton) + + let test = CGFloat((view.frame.width - (view.frame.width / 2 + 37.5)) + ((view.frame.width / 2) - 37.5) - 9.0) + + flashButton = UIButton(frame: CGRect(x: test, y: view.frame.height - 77.5, width: 18.0, height: 30.0)) + flashButton.setImage(#imageLiteral(resourceName: "flashOutline"), for: UIControlState()) + flashButton.addTarget(self, action: #selector(toggleFlashAction(_:)), for: .touchUpInside) + self.view.addSubview(flashButton) + } } diff --git a/Source/SwiftyCamViewController.swift b/Source/SwiftyCamViewController.swift index 2177789..d30d463 100644 --- a/Source/SwiftyCamViewController.swift +++ b/Source/SwiftyCamViewController.swift @@ -1,17 +1,18 @@ /*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. */ + +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 UIKit @@ -22,991 +23,1045 @@ import AVFoundation /// A UIViewController Camera View Subclass open class SwiftyCamViewController: UIViewController { - - // MARK: Enumeration Declaration - - /// Enumeration for Camera Selection - - public enum CameraSelection { - - /// Camera on the back of the device - case rear - - /// Camera on the front of the device - case front - } - - /// Enumeration for video quality of the capture session. Corresponds to a AVCaptureSessionPreset - - public enum VideoQuality { - - /// AVCaptureSessionPresetHigh - case high - - /// AVCaptureSessionPresetMedium - case medium - - /// AVCaptureSessionPresetLow - case low - - /// AVCaptureSessionPreset352x288 - case resolution352x288 - - /// AVCaptureSessionPreset640x480 - case resolution640x480 - - /// AVCaptureSessionPreset1280x720 - case resolution1280x720 - - /// AVCaptureSessionPreset1920x1080 - case resolution1920x1080 - - /// AVCaptureSessionPreset3840x2160 - case resolution3840x2160 - - /// AVCaptureSessionPresetiFrame960x540 - case iframe960x540 - - /// AVCaptureSessionPresetiFrame1280x720 - case iframe1280x720 - } - - /** - - Result from the AVCaptureSession Setup - - - success: success - - notAuthorized: User denied access to Camera of Microphone - - configurationFailed: Unknown error - */ - - fileprivate enum SessionSetupResult { - case success - case notAuthorized - case configurationFailed - } - - // MARK: Public Variable Declarations - - /// Public Camera Delegate for the Custom View Controller Subclass - - public var cameraDelegate: SwiftyCamViewControllerDelegate? - - /// Maxiumum video duration if SwiftyCamButton is used - - public var maximumVideoDuration : Double = 0.0 - - /// Video capture quality - - public var videoQuality : VideoQuality = .high - - /// Sets whether flash is enabled for photo and video capture - - public var flashEnabled = false - - /// Sets whether Pinch to Zoom is enabled for the capture session - - public var pinchToZoom = true - - /// Sets whether Tap to Focus and Tap to Adjust Exposure is enabled for the capture session - - public var tapToFocus = true - - /// Sets whether the capture session should adjust to low light conditions automatically - /// - /// Only supported on iPhone 5 and 5C - - public var lowLightBoost = true - - /// Set whether SwiftyCam should allow background audio from other applications - - public var allowBackgroundAudio = true - - /// Sets whether a double tap to switch cameras is supported - - public var doubleTapCameraSwitch = true - - /// Set default launch camera - - public var defaultCamera = CameraSelection.rear - + // MARK: Enumeration Declaration - // MARK: Public Get-only Variable Declarations - - /// Returns true if video is currently being recorded - - private(set) public var isVideoRecording = false - - /// Returns true if the capture session is currently running - - private(set) public var isSessionRunning = false - - /// Returns the CameraSelection corresponding to the currently utilized camera - - private(set) public var currentCamera = CameraSelection.rear - - // MARK: Private Constant Declarations - - /// Current Capture Session - - fileprivate let session = AVCaptureSession() - - /// Serial queue used for setting up session - - fileprivate let sessionQueue = DispatchQueue(label: "session queue", attributes: []) - - // MARK: Private Variable Declarations - - /// Variable for storing current zoom scale - - fileprivate var zoomScale = CGFloat(1.0) - - /// Variable for storing initial zoom scale before Pinch to Zoom begins - - fileprivate var beginZoomScale = CGFloat(1.0) - - /// Returns true if the torch (flash) is currently enabled - - fileprivate var isCameraTorchOn = false - - /// Variable to store result of capture session setup - - fileprivate var setupResult = SessionSetupResult.success - - /// BackgroundID variable for video recording - - fileprivate var backgroundRecordingID : UIBackgroundTaskIdentifier? = nil - - /// Video Input variable - - fileprivate var videoDeviceInput : AVCaptureDeviceInput! - - /// Movie File Output variable - - fileprivate var movieFileOutput : AVCaptureMovieFileOutput? - - /// Photo File Output variable - - fileprivate var photoFileOutput : AVCaptureStillImageOutput? - - /// Video Device variable - - fileprivate var videoDevice : AVCaptureDevice? - - /// PreviewView for the capture session - - fileprivate var previewLayer : PreviewView! - - /// UIView for front facing flash - - fileprivate var flashView : UIView? - - /// Disable view autorotation for forced portrait recorindg - - - override open var shouldAutorotate: Bool { - return false - } - - // MARK: ViewDidLoad - - /// ViewDidLoad Implementation + /// Enumeration for Camera Selection - override open func viewDidLoad() { - super.viewDidLoad() - previewLayer = PreviewView(frame: self.view.frame) + public enum CameraSelection { - // Add Gesture Recognizers - - addGestureRecognizersTo(view: previewLayer) - - self.view.addSubview(previewLayer) - previewLayer.session = session - - // Test authorization status for Camera and Micophone - - switch AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo){ - case .authorized: - - // already authorized - break - case .notDetermined: - - // not yet determined - sessionQueue.suspend() - AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo, 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: ViewDidAppear - - /// ViewDidAppear(_ animated:) Implementation + /// Camera on the back of the device + case rear - - override open func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - // Set background audio preference - - setBackgroundAudioPreference() - - sessionQueue.async { - switch self.setupResult { - case .success: - // Begin Session - self.session.startRunning() - self.isSessionRunning = self.session.isRunning - case .notAuthorized: - // Prompt to App Settings - self.promptToAppSettings() - 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 - - /// ViewDidDisappear(_ animated:) Implementation + /// Camera on the front of the device + case front + } - - override open func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - - // 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() - } - - // MARK: Public Functions - - /** - - Capture photo from current session - - UIImage will be returned with the SwiftyCamViewControllerDelegate function SwiftyCamDidTakePhoto(photo:) - - */ - - 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!) + /// Enumeration for video quality of the capture session. Corresponds to a AVCaptureSessionPreset - 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!) - } - - let videoPreviewLayerOrientation = 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(withMediaType: AVMediaTypeVideo) - - - //flip video output if front facing camera is selected - if self.currentCamera == .front { - movieFileOutputConnection?.isVideoMirrored = true - } - movieFileOutputConnection?.videoOrientation = videoPreviewLayerOrientation - - // Start recording to a temporary file. - let outputFileName = UUID().uuidString - let outputFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent((outputFileName as NSString).appendingPathExtension("mov")!) - movieFileOutput.startRecording(toOutputFileURL: 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 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 as! AVCaptureInput) - } - - 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() - } + public enum VideoQuality { - - // 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 + /// AVCaptureSessionPresetHigh + case high - fileprivate func configureVideoPreset() { - - if currentCamera == .front { - session.sessionPreset = videoInputPresetFromVideoQuality(quality: .high) - } else { - if session.canSetSessionPreset(videoInputPresetFromVideoQuality(quality: videoQuality)) { - session.sessionPreset = videoInputPresetFromVideoQuality(quality: videoQuality) - } else { - session.sessionPreset = videoInputPresetFromVideoQuality(quality: .high) - } - } - } - - /// Add Video Inputs - - fileprivate func addVideoInput() { - switch currentCamera { - case .front: - videoDevice = SwiftyCamViewController.deviceWithMediaType(AVMediaTypeVideo, preferringPosition: .front) - case .rear: - videoDevice = SwiftyCamViewController.deviceWithMediaType(AVMediaTypeVideo, 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 add video device input to the session") - print(session.canSetSessionPreset(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() { - do { - let audioDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio) - 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(withMediaType: AVMediaTypeVideo) { - 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) - - var image: UIImage! - - // Set proper orientation for photo - // If camera is currently set to front camera, flip image - - switch self.currentCamera { - case .front: - let newImage = UIImage(cgImage: cgImageRef!, scale: 1.0, orientation: .leftMirrored) - if let adjustedImage = newImage.fixOrientation() { - image = adjustedImage - } else { - image = newImage - } - case .rear: - let newImage = UIImage(cgImage: cgImageRef!, scale: 1.0, orientation: .right) - if let adjustedImage = newImage.fixOrientation() { - image = adjustedImage - } else { - image = newImage - } - } - return image - } - - fileprivate func capturePhotoAsyncronously(completionHandler: @escaping(Bool) -> ()) { - if let videoConnection = photoFileOutput?.connection(withMediaType: AVMediaTypeVideo) { - videoConnection.videoOrientation = AVCaptureVideoOrientation.portrait - 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 + /// AVCaptureSessionPresetMedium + case medium - 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 AVCaptureSessionPresetHigh - case .medium: return AVCaptureSessionPresetMedium - case .low: return AVCaptureSessionPresetLow - case .resolution352x288: return AVCaptureSessionPreset352x288 - case .resolution640x480: return AVCaptureSessionPreset640x480 - case .resolution1280x720: return AVCaptureSessionPreset1280x720 - case .resolution1920x1080: return AVCaptureSessionPreset1920x1080 - case .iframe960x540: return AVCaptureSessionPresetiFrame960x540 - case .iframe1280x720: return AVCaptureSessionPresetiFrame1280x720 - case .resolution3840x2160: - if #available(iOS 9.0, *) { - return AVCaptureSessionPreset3840x2160 - } - else { - print("[SwiftyCam]: Resolution 3840x2160 not supported") - return AVCaptureSessionPresetHigh - } - } - } - - /// Get Devices - - fileprivate class func deviceWithMediaType(_ mediaType: String, preferringPosition position: AVCaptureDevicePosition) -> AVCaptureDevice? { - if let devices = AVCaptureDevice.devices(withMediaType: mediaType) as? [AVCaptureDevice] { - return devices.filter({ $0.position == position }).first - } - return nil - } - - /// Enable or disable flash for photo - - fileprivate func changeFlashSettings(device: AVCaptureDevice, mode: AVCaptureFlashMode) { - 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.defaultDevice(withMediaType: AVMediaTypeVideo) - // Check if device has a flash - if (device?.hasTorch)! { - do { - try device?.lockForConfiguration() - if (device?.torchMode == AVCaptureTorchMode.on) { - device?.torchMode = AVCaptureTorchMode.off - self.isCameraTorchOn = false - } else { - do { - try device?.setTorchModeOnWithLevel(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 - } - - do{ - try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, - with: [.duckOthers, .defaultToSpeaker]) + /// AVCaptureSessionPresetLow + case low - session.automaticallyConfiguresApplicationAudioSession = false - } - catch { - print("[SwiftyCam]: Failed to set background audio preference") - - } - } + /// AVCaptureSessionPreset352x288 + case resolution352x288 + + /// AVCaptureSessionPreset640x480 + case resolution640x480 + + /// AVCaptureSessionPreset1280x720 + case resolution1280x720 + + /// AVCaptureSessionPreset1920x1080 + case resolution1920x1080 + + /// AVCaptureSessionPreset3840x2160 + case resolution3840x2160 + + /// AVCaptureSessionPresetiFrame960x540 + case iframe960x540 + + /// AVCaptureSessionPresetiFrame1280x720 + case iframe1280x720 + } + + /** + + Result from the AVCaptureSession Setup + + - success: success + - notAuthorized: User denied access to Camera of Microphone + - configurationFailed: Unknown error + */ + + fileprivate enum SessionSetupResult { + case success + case notAuthorized + case configurationFailed + } + + // MARK: Public Variable Declarations + + /// Public Camera Delegate for the Custom View Controller Subclass + + public var cameraDelegate: SwiftyCamViewControllerDelegate? + + /// Maxiumum video duration if SwiftyCamButton is used + + public var maximumVideoDuration : Double = 0.0 + + /// Video capture quality + + public var videoQuality : VideoQuality = .high + + /// Sets whether flash is enabled for photo and video capture + + public var flashEnabled = false + + /// Sets whether Pinch to Zoom is enabled for the capture session + + public var pinchToZoom = true + + /// Sets the maximum zoom scale allowed during Pinch gesture + + fileprivate var maxZoomScale = CGFloat(4.0) + + /// Sets whether Tap to Focus and Tap to Adjust Exposure is enabled for the capture session + + public var tapToFocus = true + + /// Sets whether the capture session should adjust to low light conditions automatically + /// + /// Only supported on iPhone 5 and 5C + + public var lowLightBoost = true + + /// Set whether SwiftyCam should allow background audio from other applications + + public var allowBackgroundAudio = true + + /// Sets whether a double tap to switch cameras is supported + + public var doubleTapCameraSwitch = true + + /// Set default launch camera + + public var defaultCamera = CameraSelection.rear + + /// Sets wether the taken photo or video should be oriented according to the device orientation + + public var shouldUseDeviceOrientation = false + + + // MARK: Public Get-only Variable Declarations + + /// Returns true if video is currently being recorded + + private(set) public var isVideoRecording = false + + /// Returns true if the capture session is currently running + + private(set) public var isSessionRunning = false + + /// Returns the CameraSelection corresponding to the currently utilized camera + + private(set) public var currentCamera = CameraSelection.rear + + // MARK: Private Constant Declarations + + /// Current Capture Session + + fileprivate let session = AVCaptureSession() + + /// Serial queue used for setting up session + + fileprivate let sessionQueue = DispatchQueue(label: "session queue", attributes: []) + + // MARK: Private Variable Declarations + + /// Variable for storing current zoom scale + + fileprivate var zoomScale = CGFloat(1.0) + + /// Variable for storing initial zoom scale before Pinch to Zoom begins + + fileprivate var beginZoomScale = CGFloat(1.0) + + /// Returns true if the torch (flash) is currently enabled + + fileprivate var isCameraTorchOn = false + + /// Variable to store result of capture session setup + + fileprivate var setupResult = SessionSetupResult.success + + /// BackgroundID variable for video recording + + fileprivate var backgroundRecordingID : UIBackgroundTaskIdentifier? = nil + + /// Video Input variable + + fileprivate var videoDeviceInput : AVCaptureDeviceInput! + + /// Movie File Output variable + + fileprivate var movieFileOutput : AVCaptureMovieFileOutput? + + /// Photo File Output variable + + fileprivate var photoFileOutput : AVCaptureStillImageOutput? + + /// Video Device variable + + fileprivate var videoDevice : AVCaptureDevice? + + /// PreviewView for the capture session + + fileprivate var previewLayer : PreviewView! + + /// UIView for front facing flash + + fileprivate var flashView : UIView? + + /// Last changed orientation + + fileprivate var deviceOrientation : UIDeviceOrientation? + + /// Disable view autorotation for forced portrait recorindg + + + override open var shouldAutorotate: Bool { + return false + } + + // MARK: ViewDidLoad + + /// ViewDidLoad Implementation + + override open func viewDidLoad() { + super.viewDidLoad() + previewLayer = PreviewView(frame: self.view.frame) + + // Add Gesture Recognizers + + addGestureRecognizersTo(view: previewLayer) + + self.view.addSubview(previewLayer) + previewLayer.session = session + + // Test authorization status for Camera and Micophone + + switch AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo){ + case .authorized: + + // already authorized + break + case .notDetermined: + + // not yet determined + sessionQueue.suspend() + AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo, 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: ViewDidAppear + + /// ViewDidAppear(_ animated:) Implementation + + + override open func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + // Subscribe to device rotation notifications + + if shouldUseDeviceOrientation { + subscribeToDeviceOrientationChangeNotifications() + } + + // Set background audio preference + + setBackgroundAudioPreference() + + sessionQueue.async { + switch self.setupResult { + case .success: + // Begin Session + self.session.startRunning() + self.isSessionRunning = self.session.isRunning + case .notAuthorized: + // Prompt to App Settings + self.promptToAppSettings() + 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 + + /// ViewDidDisappear(_ animated:) Implementation + + + override open func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + // 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() + + // Unsubscribe from device rotation notifications + if shouldUseDeviceOrientation { + unsubscribeFromDeviceOrientationChangeNotifications() + } + } + + // MARK: Public Functions + + /** + + Capture photo from current session + + UIImage will be returned with the SwiftyCamViewControllerDelegate function SwiftyCamDidTakePhoto(photo:) + + */ + + 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!) + } + + 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(withMediaType: AVMediaTypeVideo) + + + //flip video output if front facing camera is selected + if self.currentCamera == .front { + movieFileOutputConnection?.isVideoMirrored = true + } + + movieFileOutputConnection?.videoOrientation = self.getVideoOrientation() + + // Start recording to a temporary file. + let outputFileName = UUID().uuidString + let outputFilePath = (NSTemporaryDirectory() as NSString).appendingPathComponent((outputFileName as NSString).appendingPathExtension("mov")!) + movieFileOutput.startRecording(toOutputFileURL: 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 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 as! AVCaptureInput) + } + + 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 = videoInputPresetFromVideoQuality(quality: .high) + } else { + if session.canSetSessionPreset(videoInputPresetFromVideoQuality(quality: videoQuality)) { + session.sessionPreset = videoInputPresetFromVideoQuality(quality: videoQuality) + } else { + session.sessionPreset = videoInputPresetFromVideoQuality(quality: .high) + } + } + } + + /// Add Video Inputs + + fileprivate func addVideoInput() { + switch currentCamera { + case .front: + videoDevice = SwiftyCamViewController.deviceWithMediaType(AVMediaTypeVideo, preferringPosition: .front) + case .rear: + videoDevice = SwiftyCamViewController.deviceWithMediaType(AVMediaTypeVideo, 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 add video device input to the session") + print(session.canSetSessionPreset(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() { + do { + let audioDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeAudio) + 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(withMediaType: AVMediaTypeVideo) { + 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 + } + } + + /// 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 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. + + - 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.getImageOrientation(forCamera: self.currentCamera)) + + return image + } + + fileprivate func capturePhotoAsyncronously(completionHandler: @escaping(Bool) -> ()) { + if let videoConnection = photoFileOutput?.connection(withMediaType: AVMediaTypeVideo) { + + 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 AVCaptureSessionPresetHigh + case .medium: return AVCaptureSessionPresetMedium + case .low: return AVCaptureSessionPresetLow + case .resolution352x288: return AVCaptureSessionPreset352x288 + case .resolution640x480: return AVCaptureSessionPreset640x480 + case .resolution1280x720: return AVCaptureSessionPreset1280x720 + case .resolution1920x1080: return AVCaptureSessionPreset1920x1080 + case .iframe960x540: return AVCaptureSessionPresetiFrame960x540 + case .iframe1280x720: return AVCaptureSessionPresetiFrame1280x720 + case .resolution3840x2160: + if #available(iOS 9.0, *) { + return AVCaptureSessionPreset3840x2160 + } + else { + print("[SwiftyCam]: Resolution 3840x2160 not supported") + return AVCaptureSessionPresetHigh + } + } + } + + /// Get Devices + + fileprivate class func deviceWithMediaType(_ mediaType: String, preferringPosition position: AVCaptureDevicePosition) -> AVCaptureDevice? { + if let devices = AVCaptureDevice.devices(withMediaType: mediaType) as? [AVCaptureDevice] { + return devices.filter({ $0.position == position }).first + } + return nil + } + + /// Enable or disable flash for photo + + fileprivate func changeFlashSettings(device: AVCaptureDevice, mode: AVCaptureFlashMode) { + 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.defaultDevice(withMediaType: AVMediaTypeVideo) + // Check if device has a flash + if (device?.hasTorch)! { + do { + try device?.lockForConfiguration() + if (device?.torchMode == AVCaptureTorchMode.on) { + device?.torchMode = AVCaptureTorchMode.off + self.isCameraTorchOn = false + } else { + do { + try device?.setTorchModeOnWithLevel(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 + } + + do{ + try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, + with: [.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() - } + /// 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 - - public func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) { - if let currentBackgroundRecordingID = backgroundRecordingID { - backgroundRecordingID = UIBackgroundTaskInvalid - - if currentBackgroundRecordingID != UIBackgroundTaskInvalid { - UIApplication.shared.endBackgroundTask(currentBackgroundRecordingID) - } - } - if error != nil { - print("[SwiftyCam]: Movie file finishing error: \(error)") - } else { - //Call delegate function with the URL of the outputfile - DispatchQueue.main.async { - self.cameraDelegate?.swiftyCam(self, didFinishProcessVideoAt: outputFileURL) - } - } - } + + /// Process newly captured video and write it to temporary directory + + public func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) { + if let currentBackgroundRecordingID = backgroundRecordingID { + backgroundRecordingID = UIBackgroundTaskInvalid + + if currentBackgroundRecordingID != UIBackgroundTaskInvalid { + UIApplication.shared.endBackgroundTask(currentBackgroundRecordingID) + } + } + if error != nil { + print("[SwiftyCam]: Movie file finishing error: \(error)") + } else { + //Call delegate function with the URL of the outputfile + DispatchQueue.main.async { + self.cameraDelegate?.swiftyCam(self, didFinishProcessVideoAt: outputFileURL) + } + } + } } // Mark: UIGestureRecognizer Declarations extension SwiftyCamViewController { - - /// Handle pinch gesture - - @objc fileprivate func zoomGesture(pinch: UIPinchGestureRecognizer) { - guard pinchToZoom == true && self.currentCamera == .rear else { - //ignore pinch if pinchToZoom is set to false - return - } - do { - let captureDevice = AVCaptureDevice.devices().first as? AVCaptureDevice - try captureDevice?.lockForConfiguration() - - zoomScale = max(1.0, min(beginZoomScale * pinch.scale, captureDevice!.activeFormat.videoMaxZoomFactor)) - - captureDevice?.videoZoomFactor = zoomScale - - // Call Delegate function with current zoom scale - DispatchQueue.main.async { - self.cameraDelegate?.swiftyCam(self, didChangeZoomLevel: self.zoomScale) - } - - captureDevice?.unlockForConfiguration() - - } 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 = AVCaptureExposureMode.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() - } - - /** - Add pinch gesture recognizer and double tap gesture recognizer to currentView - - - Parameter view: View to add gesture recognzier - - */ - - fileprivate func addGestureRecognizersTo(view: UIView) { - let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(zoomGesture(pinch:))) - pinchGesture.delegate = self - view.addGestureRecognizer(pinchGesture) - - let singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(singleTapGesture(tap:))) - singleTapGesture.numberOfTapsRequired = 1 - singleTapGesture.delegate = self - view.addGestureRecognizer(singleTapGesture) - - let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(doubleTapGesture(tap:))) - doubleTapGesture.numberOfTapsRequired = 2 - doubleTapGesture.delegate = self - view.addGestureRecognizer(doubleTapGesture) - } + + /// Handle pinch gesture + + @objc fileprivate func zoomGesture(pinch: UIPinchGestureRecognizer) { + guard pinchToZoom == true && self.currentCamera == .rear else { + //ignore pinch if pinchToZoom is set to false + 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))) + + captureDevice?.videoZoomFactor = zoomScale + + // Call Delegate function with current zoom scale + DispatchQueue.main.async { + self.cameraDelegate?.swiftyCam(self, didChangeZoomLevel: self.zoomScale) + } + + captureDevice?.unlockForConfiguration() + + } 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 = AVCaptureExposureMode.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() + } + + /** + Add pinch gesture recognizer and double tap gesture recognizer to currentView + + - Parameter view: View to add gesture recognzier + + */ + + fileprivate func addGestureRecognizersTo(view: UIView) { + let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(zoomGesture(pinch:))) + pinchGesture.delegate = self + view.addGestureRecognizer(pinchGesture) + + let singleTapGesture = UITapGestureRecognizer(target: self, action: #selector(singleTapGesture(tap:))) + singleTapGesture.numberOfTapsRequired = 1 + singleTapGesture.delegate = self + view.addGestureRecognizer(singleTapGesture) + + let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(doubleTapGesture(tap:))) + doubleTapGesture.numberOfTapsRequired = 2 + doubleTapGesture.delegate = self + view.addGestureRecognizer(doubleTapGesture) + } } // MARK: UIGestureRecognizerDelegate extension SwiftyCamViewController : UIGestureRecognizerDelegate { - - /// Set beginZoomScale when pinch begins - - public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - if gestureRecognizer.isKind(of: UIPinchGestureRecognizer.self) { - beginZoomScale = zoomScale; - } - return true - } + + /// Set beginZoomScale when pinch begins + + public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer.isKind(of: UIPinchGestureRecognizer.self) { + beginZoomScale = zoomScale; + } + return true + } }