diff --git a/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/project.pbxproj b/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/project.pbxproj index 6af0d11..7872b96 100644 --- a/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/project.pbxproj +++ b/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 056AAB091F97CB1700F6A978 /* Orientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 056AAB081F97CB1700F6A978 /* Orientation.swift */; }; + 056AAB0A1F97CB1E00F6A978 /* Orientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 056AAB081F97CB1700F6A978 /* Orientation.swift */; }; 05D2A9B81E80BE9700B479E9 /* SwiftyCam-iOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 05D2A9B61E80BE9700B479E9 /* SwiftyCam-iOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; 05D2A9BC1E80BE9D00B479E9 /* PreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1675A9891E00A74A00B80903 /* PreviewView.swift */; }; 05D2A9BD1E80BE9D00B479E9 /* SwiftyCamButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1675A98A1E00A74A00B80903 /* SwiftyCamButton.swift */; }; @@ -27,6 +29,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 056AAB081F97CB1700F6A978 /* Orientation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Orientation.swift; path = ../../Source/Orientation.swift; sourceTree = ""; }; 05D2A9B41E80BE9700B479E9 /* SwiftyCam.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyCam.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 05D2A9B61E80BE9700B479E9 /* SwiftyCam-iOS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SwiftyCam-iOS.h"; sourceTree = ""; }; 05D2A9B71E80BE9700B479E9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -111,6 +114,7 @@ 1675A9911E00A74F00B80903 /* Source */ = { isa = PBXGroup; children = ( + 056AAB081F97CB1700F6A978 /* Orientation.swift */, 1675A9891E00A74A00B80903 /* PreviewView.swift */, 1675A98A1E00A74A00B80903 /* SwiftyCamButton.swift */, 1675A98B1E00A74A00B80903 /* SwiftyCamViewController.swift */, @@ -175,7 +179,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0810; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 1010; ORGANIZATIONNAME = Cappsule; TargetAttributes = { 05D2A9B31E80BE9700B479E9 = { @@ -184,7 +188,7 @@ }; 1675A9711E00A68300B80903 = { CreatedOnToolsVersion = 8.1; - DevelopmentTeam = DGV5BLXSF9; + LastSwiftMigration = 1010; ProvisioningStyle = Automatic; }; }; @@ -234,6 +238,7 @@ buildActionMask = 2147483647; files = ( 05D2A9BC1E80BE9D00B479E9 /* PreviewView.swift in Sources */, + 056AAB0A1F97CB1E00F6A978 /* Orientation.swift in Sources */, 05D2A9BF1E80BE9D00B479E9 /* SwiftyCamViewControllerDelegate.swift in Sources */, 05D2A9BE1E80BE9D00B479E9 /* SwiftyCamViewController.swift in Sources */, 05D2A9BD1E80BE9D00B479E9 /* SwiftyCamButton.swift in Sources */, @@ -250,6 +255,7 @@ 1675A9761E00A68300B80903 /* AppDelegate.swift in Sources */, 1675A98F1E00A74A00B80903 /* SwiftyCamViewController.swift in Sources */, 168505EA1E288D80005B4537 /* PhotoViewController.swift in Sources */, + 056AAB091F97CB1700F6A978 /* Orientation.swift in Sources */, 16298B561E2703DC0056D413 /* SwiftyRecordButton.swift in Sources */, 1675A98D1E00A74A00B80903 /* PreviewView.swift in Sources */, 1675A98E1E00A74A00B80903 /* SwiftyCamButton.swift in Sources */, @@ -294,7 +300,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.Cappsule.SwiftyCam-iOS"; PRODUCT_NAME = SwiftyCam; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -317,7 +323,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.Cappsule.SwiftyCam-iOS"; PRODUCT_NAME = SwiftyCam; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -333,15 +339,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -371,6 +385,8 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -383,15 +399,23 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; @@ -413,6 +437,8 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; VALIDATE_PRODUCT = YES; }; name = Release; @@ -421,13 +447,13 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = DGV5BLXSF9; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = DemoSwiftyCam/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.Walzy.DemoSwiftyCam; + PRODUCT_BUNDLE_IDENTIFIER = com.Walzy.DemoSwiftyCam1; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -436,13 +462,13 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = DGV5BLXSF9; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = DemoSwiftyCam/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = com.Walzy.DemoSwiftyCam; + PRODUCT_BUNDLE_IDENTIFIER = com.Walzy.DemoSwiftyCam1; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; diff --git a/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/xcshareddata/xcschemes/SwiftyCam-iOS.xcscheme b/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/xcshareddata/xcschemes/SwiftyCam-iOS.xcscheme index 95275f3..f1c1bc6 100644 --- a/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/xcshareddata/xcschemes/SwiftyCam-iOS.xcscheme +++ b/DemoSwiftyCam/DemoSwiftyCam.xcodeproj/xcshareddata/xcschemes/SwiftyCam-iOS.xcscheme @@ -1,6 +1,6 @@ @@ -45,6 +46,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" 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/Info.plist b/DemoSwiftyCam/DemoSwiftyCam/Info.plist index e3b8756..e3fcada 100644 --- a/DemoSwiftyCam/DemoSwiftyCam/Info.plist +++ b/DemoSwiftyCam/DemoSwiftyCam/Info.plist @@ -2,6 +2,8 @@ + NSPhotoLibraryUsageDescription + To save videos CFBundleDevelopmentRegion en CFBundleExecutable diff --git a/DemoSwiftyCam/DemoSwiftyCam/PhotoViewController.swift b/DemoSwiftyCam/DemoSwiftyCam/PhotoViewController.swift index 89dc74a..1667af5 100644 --- a/DemoSwiftyCam/DemoSwiftyCam/PhotoViewController.swift +++ b/DemoSwiftyCam/DemoSwiftyCam/PhotoViewController.swift @@ -36,16 +36,16 @@ 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) } - func cancel() { + @objc func cancel() { dismiss(animated: true, completion: nil) } } diff --git a/DemoSwiftyCam/DemoSwiftyCam/VideoViewController.swift b/DemoSwiftyCam/DemoSwiftyCam/VideoViewController.swift index ce55aa9..8121d1a 100644 --- a/DemoSwiftyCam/DemoSwiftyCam/VideoViewController.swift +++ b/DemoSwiftyCam/DemoSwiftyCam/VideoViewController.swift @@ -49,15 +49,32 @@ 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 { + if #available(iOS 10.0, *) { + try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient, mode: .default, options: []) + } else { + } + } catch let error as NSError { + print(error) + } + + do { + try AVAudioSession.sharedInstance().setActive(true) + } catch let error as NSError { + print(error) + } } override func viewDidAppear(_ animated: Bool) { @@ -65,14 +82,19 @@ class VideoViewController: UIViewController { player?.play() } - func cancel() { + @objc func cancel() { dismiss(animated: true, completion: nil) } @objc fileprivate func playerItemDidReachEnd(_ notification: Notification) { if self.player != nil { - self.player!.seek(to: kCMTimeZero) + self.player!.seek(to: CMTime.zero) self.player!.play() } } } + +// Helper function inserted by Swift 4.2 migrator. +fileprivate func convertFromAVAudioSessionCategory(_ input: AVAudioSession.Category) -> String { + return input.rawValue +} diff --git a/DemoSwiftyCam/DemoSwiftyCam/ViewController.swift b/DemoSwiftyCam/DemoSwiftyCam/ViewController.swift index 8d3e90f..06a5d9b 100644 --- a/DemoSwiftyCam/DemoSwiftyCam/ViewController.swift +++ b/DemoSwiftyCam/DemoSwiftyCam/ViewController.swift @@ -15,21 +15,25 @@ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF import UIKit +import AVFoundation class ViewController: SwiftyCamViewController, SwiftyCamViewControllerDelegate { - @IBOutlet weak var captureButton: SwiftyRecordButton! - @IBOutlet weak var flipCameraButton: UIButton! - @IBOutlet weak var flashButton: UIButton! - + @IBOutlet weak var captureButton : SwiftyRecordButton! + @IBOutlet weak var flipCameraButton : UIButton! + @IBOutlet weak var flashButton : UIButton! override func viewDidLoad() { super.viewDidLoad() + shouldPrompToAppSettings = true cameraDelegate = self maximumVideoDuration = 10.0 shouldUseDeviceOrientation = true allowAutoRotate = true audioEnabled = true + + // disable capture button until session starts + captureButton.buttonEnabled = false } override var prefersStatusBarHidden: Bool { @@ -40,6 +44,17 @@ class ViewController: SwiftyCamViewController, SwiftyCamViewControllerDelegate { super.viewDidAppear(animated) captureButton.delegate = self } + + func swiftyCamSessionDidStartRunning(_ swiftyCam: SwiftyCamViewController) { + print("Session did start running") + captureButton.buttonEnabled = true + } + + func swiftyCamSessionDidStopRunning(_ swiftyCam: SwiftyCamViewController) { + print("Session did stop running") + captureButton.buttonEnabled = false + } + func swiftyCam(_ swiftyCam: SwiftyCamViewController, didTake photo: UIImage) { let newVC = PhotoViewController(image: photo) @@ -49,19 +64,13 @@ class ViewController: SwiftyCamViewController, SwiftyCamViewControllerDelegate { 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 - }) + hideButtons() } 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 - }) + showButtons() } func swiftyCam(_ swiftyCam: SwiftyCamViewController, didFinishProcessVideoAt url: URL) { @@ -70,29 +79,24 @@ class ViewController: SwiftyCamViewController, SwiftyCamViewControllerDelegate { } 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() - }) - }) + print("Did focus at point: \(point)") + focusAnimationAt(point) } + + func swiftyCamDidFailToConfigure(_ swiftyCam: SwiftyCamViewController) { + 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)) + present(alertController, animated: true, completion: nil) + } func swiftyCam(_ swiftyCam: SwiftyCamViewController, didChangeZoomLevel zoom: CGFloat) { + print("Zoom level did change. Level: \(zoom)") print(zoom) } func swiftyCam(_ swiftyCam: SwiftyCamViewController, didSwitchCameras camera: SwiftyCamViewController.CameraSelection) { + print("Camera did change to \(camera.rawValue)") print(camera) } @@ -106,11 +110,52 @@ class ViewController: SwiftyCamViewController, SwiftyCamViewControllerDelegate { @IBAction func toggleFlashTapped(_ sender: Any) { flashEnabled = !flashEnabled + toggleFlashAnimation() + } +} + + +// UI Animations +extension ViewController { + + fileprivate func hideButtons() { + UIView.animate(withDuration: 0.25) { + self.flashButton.alpha = 0.0 + self.flipCameraButton.alpha = 0.0 + } + } + + fileprivate func showButtons() { + UIView.animate(withDuration: 0.25) { + self.flashButton.alpha = 1.0 + self.flipCameraButton.alpha = 1.0 + } + } + + fileprivate func focusAnimationAt(_ 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) + }) { (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) + }) { (success) in + focusView.removeFromSuperview() + } + } + } + + 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 new file mode 100644 index 0000000..578d475 --- /dev/null +++ b/Source/Orientation.swift @@ -0,0 +1,110 @@ +/*Copyright (c) 2016, Andrew Walz. + + Redistribution and use in source and binary forms, with or without modification,are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +import Foundation +import AVFoundation +import UIKit +import CoreMotion + + +class Orientation { + + var shouldUseDeviceOrientation: Bool = false + + fileprivate var deviceOrientation : UIDeviceOrientation? + fileprivate let coreMotionManager = CMMotionManager() + + init() { + coreMotionManager.accelerometerUpdateInterval = 0.1 + } + + func start() { + self.deviceOrientation = UIDevice.current.orientation + coreMotionManager.startAccelerometerUpdates(to: .main) { [weak self] (data, error) in + guard let data = data else { + return + } + self?.handleAccelerometerUpdate(data: data) + } + } + + func stop() { + self.coreMotionManager.stopAccelerometerUpdates() + self.deviceOrientation = nil + } + + func getImageOrientation(forCamera: SwiftyCamViewController.CameraSelection) -> UIImage.Orientation { + guard shouldUseDeviceOrientation, let deviceOrientation = self.deviceOrientation else { return forCamera == .rear ? .right : .leftMirrored } + + switch deviceOrientation { + case .landscapeLeft: + return forCamera == .rear ? .up : .downMirrored + case .landscapeRight: + return forCamera == .rear ? .down : .upMirrored + case .portraitUpsideDown: + return forCamera == .rear ? .left : .rightMirrored + default: + return forCamera == .rear ? .right : .leftMirrored + } + } + + func getPreviewLayerOrientation() -> AVCaptureVideoOrientation { + // Depends on layout orientation, not device orientation + switch UIApplication.shared.statusBarOrientation { + case .portrait, .unknown: + return AVCaptureVideoOrientation.portrait + case .landscapeLeft: + return AVCaptureVideoOrientation.landscapeLeft + case .landscapeRight: + return AVCaptureVideoOrientation.landscapeRight + case .portraitUpsideDown: + return AVCaptureVideoOrientation.portraitUpsideDown + } + } + + func getVideoOrientation() -> AVCaptureVideoOrientation? { + guard shouldUseDeviceOrientation, let deviceOrientation = self.deviceOrientation else { return nil } + + switch deviceOrientation { + case .landscapeLeft: + return .landscapeRight + case .landscapeRight: + return .landscapeLeft + case .portraitUpsideDown: + return .portraitUpsideDown + default: + return .portrait + } + } + + private func handleAccelerometerUpdate(data: CMAccelerometerData){ + if(abs(data.acceleration.y) < abs(data.acceleration.x)){ + if(data.acceleration.x > 0){ + deviceOrientation = UIDeviceOrientation.landscapeRight + } else { + deviceOrientation = UIDeviceOrientation.landscapeLeft + } + } else{ + if(data.acceleration.y > 0){ + deviceOrientation = UIDeviceOrientation.portraitUpsideDown + } else { + deviceOrientation = UIDeviceOrientation.portrait + } + } + } +} + + + diff --git a/Source/PreviewView.swift b/Source/PreviewView.swift index f555d97..2b62c26 100644 --- a/Source/PreviewView.swift +++ b/Source/PreviewView.swift @@ -55,11 +55,11 @@ class PreviewView: UIView { let previewlayer = layer as! AVCaptureVideoPreviewLayer switch gravity { case .resize: - previewlayer.videoGravity = AVLayerVideoGravityResize + previewlayer.videoGravity = AVLayerVideoGravity.resize case .resizeAspect: - previewlayer.videoGravity = AVLayerVideoGravityResizeAspect + previewlayer.videoGravity = AVLayerVideoGravity.resizeAspect case .resizeAspectFill: - previewlayer.videoGravity = AVLayerVideoGravityResizeAspectFill + previewlayer.videoGravity = AVLayerVideoGravity.resizeAspectFill } return previewlayer } diff --git a/Source/SwiftyCamButton.swift b/Source/SwiftyCamButton.swift index dce4162..a9e2448 100644 --- a/Source/SwiftyCamButton.swift +++ b/Source/SwiftyCamButton.swift @@ -54,6 +54,10 @@ open class SwiftyCamButton: UIButton { public weak var delegate: SwiftyCamButtonDelegate? + // Sets whether button is enabled + + public var buttonEnabled = true + /// Maximum duration variable fileprivate var timer : Timer? @@ -76,11 +80,19 @@ open class SwiftyCamButton: UIButton { /// UITapGestureRecognizer Function @objc fileprivate func Tap() { + guard buttonEnabled == true else { + return + } + delegate?.buttonWasTapped() } /// UILongPressGestureRecognizer Function @objc fileprivate func LongPress(_ sender:UILongPressGestureRecognizer!) { + guard buttonEnabled == true else { + return + } + switch sender.state { case .began: delegate?.buttonDidBeginLongPress() diff --git a/Source/SwiftyCamViewController.swift b/Source/SwiftyCamViewController.swift index 1d058ca..52a4f4b 100644 --- a/Source/SwiftyCamViewController.swift +++ b/Source/SwiftyCamViewController.swift @@ -27,13 +27,13 @@ open class SwiftyCamViewController: UIViewController { /// Enumeration for Camera Selection - public enum CameraSelection { + public enum CameraSelection: String { /// Camera on the back of the device - case rear + case rear = "rear" /// Camera on the front of the device - case front + case front = "front" } /// Enumeration for video quality of the capture session. Corresponds to a AVCaptureSessionPreset @@ -112,7 +112,7 @@ open class SwiftyCamViewController: UIViewController { /// Sets the maximum zoom scale allowed during gestures gesture public var maxZoomScale = CGFloat.greatestFiniteMagnitude - + /// Sets whether Tap to Focus and Tap to Adjust Exposure is enabled for the capture session public var tapToFocus = true @@ -130,13 +130,13 @@ open class SwiftyCamViewController: UIViewController { /// Sets whether a double tap to switch cameras is supported public var doubleTapCameraSwitch = true - + /// Sets whether swipe vertically to zoom is supported - + public var swipeToZoom = true - + /// Sets whether swipe vertically gestures should be inverted - + public var swipeToZoomInverted = false /// Set default launch camera @@ -145,22 +145,30 @@ open class SwiftyCamViewController: UIViewController { /// Sets wether the taken photo or video should be oriented according to the device orientation - public var shouldUseDeviceOrientation = false - + public var shouldUseDeviceOrientation = false { + didSet { + orientation.shouldUseDeviceOrientation = shouldUseDeviceOrientation + } + } + /// Sets whether or not View Controller supports auto rotation - + public var allowAutoRotate = false - + /// Specifies the [videoGravity](https://developer.apple.com/reference/avfoundation/avcapturevideopreviewlayer/1386708-videogravity) for the preview layer. public var videoGravity : SwiftyCamVideoGravity = .resizeAspect - + /// 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 - + + /// 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 + public var shouldPrompToAppSettings = true + /// Public access to Pinch Gesture fileprivate(set) public var pinchGesture : UIPinchGestureRecognizer! - + /// Public access to Pan Gesture fileprivate(set) public var panGesture : UIPanGestureRecognizer! @@ -234,14 +242,18 @@ open class SwiftyCamViewController: UIViewController { /// UIView for front facing flash fileprivate var flashView : UIView? - + /// Pan Translation - + fileprivate var previousPanTranslation : CGFloat = 0.0 /// Last changed orientation - fileprivate var deviceOrientation : UIDeviceOrientation? + fileprivate var orientation : Orientation = Orientation() + + /// Boolean to store when View Controller is notified session is running + + fileprivate var sessionRunning = false /// Disable view autorotation for forced portrait recorindg @@ -258,17 +270,17 @@ open class SwiftyCamViewController: UIViewController { previewLayer = PreviewView(frame: view.frame, videoGravity: videoGravity) previewLayer.center = view.center view.addSubview(previewLayer) - view.sendSubview(toBack: previewLayer) + view.sendSubviewToBack(previewLayer) // Add Gesture Recognizers - + addGestureRecognizers() previewLayer.session = session // Test authorization status for Camera and Micophone - switch AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo){ + switch AVCaptureDevice.authorizationStatus(for: AVMediaType.video) { case .authorized: // already authorized @@ -277,7 +289,7 @@ open class SwiftyCamViewController: UIViewController { // not yet determined sessionQueue.suspend() - AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo, completionHandler: { [unowned self] granted in + AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { [unowned self] granted in if !granted { self.setupResult = .notAuthorized } @@ -294,65 +306,74 @@ open class SwiftyCamViewController: UIViewController { } // MARK: ViewDidLayoutSubviews - + /// ViewDidLayoutSubviews() Implementation private func updatePreviewLayer(layer: AVCaptureConnection, orientation: AVCaptureVideoOrientation) { - + layer.videoOrientation = orientation - + previewLayer.frame = self.view.bounds - + } - + override open func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() - + if let connection = self.previewLayer?.videoPreviewLayer.connection { - + let currentDevice: UIDevice = UIDevice.current - + let orientation: UIDeviceOrientation = currentDevice.orientation - + let previewLayerConnection : AVCaptureConnection = connection - + if previewLayerConnection.isVideoOrientationSupported { - + switch (orientation) { case .portrait: updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait) - + break - + case .landscapeRight: updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeLeft) - + break - + case .landscapeLeft: updatePreviewLayer(layer: previewLayerConnection, orientation: .landscapeRight) - + break - + case .portraitUpsideDown: updatePreviewLayer(layer: previewLayerConnection, orientation: .portraitUpsideDown) - + break - + default: updatePreviewLayer(layer: previewLayerConnection, orientation: .portrait) - + break } } } } + + // MARK: ViewWillAppear + + /// ViewWillAppear(_ animated:) Implementation + + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + NotificationCenter.default.addObserver(self, selector: #selector(captureSessionDidStartRunning), name: .AVCaptureSessionDidStartRunning, object: nil) + 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) // Subscribe to device rotation notifications if shouldUseDeviceOrientation { - subscribeToDeviceOrientationChangeNotifications() + orientation.start() } // Set background audio preference @@ -365,23 +386,23 @@ open class SwiftyCamViewController: UIViewController { // 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.getPreviewLayerOrientation() + self.previewLayer.videoPreviewLayer.connection?.videoOrientation = self.orientation.getPreviewLayerOrientation() } - + case .notAuthorized: - // Prompt to App Settings - self.promptToAppSettings() + if self.shouldPrompToAppSettings == true { + self.promptToAppSettings() + } else { + self.cameraDelegate?.swiftyCamNotAuthorized(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) - }) + DispatchQueue.main.async { + self.cameraDelegate?.swiftyCamDidFailToConfigure(self) + } } } } @@ -394,6 +415,9 @@ open class SwiftyCamViewController: UIViewController { 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() @@ -405,7 +429,7 @@ open class SwiftyCamViewController: UIViewController { // Unsubscribe from device rotation notifications if shouldUseDeviceOrientation { - unsubscribeFromDeviceOrientationChangeNotifications() + orientation.stop() } } @@ -425,6 +449,7 @@ open class SwiftyCamViewController: UIViewController { 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 }) @@ -464,6 +489,11 @@ open class SwiftyCamViewController: UIViewController { */ 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 } @@ -479,6 +509,9 @@ open class SwiftyCamViewController: UIViewController { 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 { @@ -486,7 +519,7 @@ open class SwiftyCamViewController: UIViewController { } // Update the orientation on the movie file output video connection before starting recording. - let movieFileOutputConnection = self.movieFileOutput?.connection(withMediaType: AVMediaTypeVideo) + let movieFileOutputConnection = self.movieFileOutput?.connection(with: AVMediaType.video) //flip video output if front facing camera is selected @@ -494,12 +527,12 @@ open class SwiftyCamViewController: UIViewController { movieFileOutputConnection?.isVideoMirrored = true } - movieFileOutputConnection?.videoOrientation = self.getVideoOrientation() + 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(toOutputFileURL: URL(fileURLWithPath: outputFilePath), recordingDelegate: self) + movieFileOutput.startRecording(to: URL(fileURLWithPath: outputFilePath), recordingDelegate: self) self.isVideoRecording = true DispatchQueue.main.async { self.cameraDelegate?.swiftyCam(self, didBeginRecordingVideo: self.currentCamera) @@ -555,11 +588,11 @@ open class SwiftyCamViewController: UIViewController { print("[SwiftyCam]: Switching between cameras while recording video is not supported") return } - + guard session.isRunning == true else { return } - + switch currentCamera { case .front: currentCamera = .rear @@ -574,7 +607,7 @@ open class SwiftyCamViewController: UIViewController { // remove and re-add inputs and outputs for input in self.session.inputs { - self.session.removeInput(input as! AVCaptureInput) + self.session.removeInput(input ) } self.addInputs() @@ -631,12 +664,12 @@ open class SwiftyCamViewController: UIViewController { fileprivate func configureVideoPreset() { if currentCamera == .front { - session.sessionPreset = videoInputPresetFromVideoQuality(quality: .high) + session.sessionPreset = AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: .high)) } else { - if session.canSetSessionPreset(videoInputPresetFromVideoQuality(quality: videoQuality)) { - session.sessionPreset = videoInputPresetFromVideoQuality(quality: videoQuality) + if session.canSetSessionPreset(AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: videoQuality))) { + session.sessionPreset = AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: videoQuality)) } else { - session.sessionPreset = videoInputPresetFromVideoQuality(quality: .high) + session.sessionPreset = AVCaptureSession.Preset(rawValue: videoInputPresetFromVideoQuality(quality: .high)) } } } @@ -646,9 +679,9 @@ open class SwiftyCamViewController: UIViewController { fileprivate func addVideoInput() { switch currentCamera { case .front: - videoDevice = SwiftyCamViewController.deviceWithMediaType(AVMediaTypeVideo, preferringPosition: .front) + videoDevice = SwiftyCamViewController.deviceWithMediaType(AVMediaType.video.rawValue, preferringPosition: .front) case .rear: - videoDevice = SwiftyCamViewController.deviceWithMediaType(AVMediaTypeVideo, preferringPosition: .back) + videoDevice = SwiftyCamViewController.deviceWithMediaType(AVMediaType.video.rawValue, preferringPosition: .back) } if let device = videoDevice { @@ -680,18 +713,20 @@ open class SwiftyCamViewController: UIViewController { } 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 - } + 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 + } + } + } catch { print("[SwiftyCam]: Could not create video device input: \(error)") setupResult = .configurationFailed @@ -706,17 +741,19 @@ open class SwiftyCamViewController: UIViewController { return } 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 { + 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") + } + + } else { + print("[SwiftyCam]: Could not find an audio device") + } + + } catch { print("[SwiftyCam]: Could not create audio device input: \(error)") } } @@ -728,7 +765,7 @@ open class SwiftyCamViewController: UIViewController { if self.session.canAddOutput(movieFileOutput) { self.session.addOutput(movieFileOutput) - if let connection = movieFileOutput.connection(withMediaType: AVMediaTypeVideo) { + if let connection = movieFileOutput.connection(with: AVMediaType.video) { if connection.isVideoStabilizationSupported { connection.preferredVideoStabilizationMode = .auto } @@ -749,67 +786,6 @@ open class SwiftyCamViewController: UIViewController { } } - /// Orientation management - - fileprivate func subscribeToDeviceOrientationChangeNotifications() { - self.deviceOrientation = UIDevice.current.orientation - NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) - } - - fileprivate func unsubscribeFromDeviceOrientationChangeNotifications() { - NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil) - self.deviceOrientation = nil - } - - @objc fileprivate func deviceDidRotate() { - if !UIDevice.current.orientation.isFlat { - self.deviceOrientation = UIDevice.current.orientation - } - } - - fileprivate func getPreviewLayerOrientation() -> AVCaptureVideoOrientation { - // Depends on layout orientation, not device orientation - switch UIApplication.shared.statusBarOrientation { - case .portrait, .unknown: - return AVCaptureVideoOrientation.portrait - case .landscapeLeft: - return AVCaptureVideoOrientation.landscapeLeft - case .landscapeRight: - return AVCaptureVideoOrientation.landscapeRight - case .portraitUpsideDown: - return AVCaptureVideoOrientation.portraitUpsideDown - } - } - - fileprivate func getVideoOrientation() -> AVCaptureVideoOrientation { - guard shouldUseDeviceOrientation, let deviceOrientation = self.deviceOrientation else { return previewLayer!.videoPreviewLayer.connection.videoOrientation } - - switch deviceOrientation { - case .landscapeLeft: - return .landscapeRight - case .landscapeRight: - return .landscapeLeft - case .portraitUpsideDown: - return .portraitUpsideDown - default: - return .portrait - } - } - - fileprivate func getImageOrientation(forCamera: CameraSelection) -> UIImageOrientation { - guard shouldUseDeviceOrientation, let deviceOrientation = self.deviceOrientation else { return forCamera == .rear ? .right : .leftMirrored } - - switch deviceOrientation { - case .landscapeLeft: - return forCamera == .rear ? .up : .downMirrored - case .landscapeRight: - return forCamera == .rear ? .down : .upMirrored - case .portraitUpsideDown: - return forCamera == .rear ? .left : .rightMirrored - default: - return forCamera == .rear ? .right : .leftMirrored - } - } /** Returns a UIImage from Image Data. @@ -826,17 +802,23 @@ open class SwiftyCamViewController: UIViewController { // Set proper orientation for photo // If camera is currently set to front camera, flip image - let image = UIImage(cgImage: cgImageRef!, scale: 1.0, orientation: self.getImageOrientation(forCamera: self.currentCamera)) + let image = UIImage(cgImage: cgImageRef!, scale: 1.0, orientation: self.orientation.getImageOrientation(forCamera: self.currentCamera)) return image } fileprivate func capturePhotoAsyncronously(completionHandler: @escaping(Bool) -> ()) { - if let videoConnection = photoFileOutput?.connection(withMediaType: AVMediaTypeVideo) { + + 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 imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer!) let image = self.processPhoto(imageData!) // Call delegate and return new image @@ -864,9 +846,9 @@ open class SwiftyCamViewController: UIViewController { 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)!) + UIApplication.shared.openURL(URL(string: UIApplication.openSettingsURLString)!) } else { - if let appSettings = URL(string: UIApplicationOpenSettingsURLString) { + if let appSettings = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.openURL(appSettings) } } @@ -885,38 +867,54 @@ open class SwiftyCamViewController: UIViewController { 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 .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 AVCaptureSessionPreset3840x2160 + return AVCaptureSession.Preset.hd4K3840x2160.rawValue } else { print("[SwiftyCam]: Resolution 3840x2160 not supported") - return AVCaptureSessionPresetHigh + return AVCaptureSession.Preset.high.rawValue } } } /// 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 + 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 nil + + //return AVCaptureDevice.devices(for: AVMediaType(rawValue: mediaType), position: position).first } /// Enable or disable flash for photo - fileprivate func changeFlashSettings(device: AVCaptureDevice, mode: AVCaptureFlashMode) { + fileprivate func changeFlashSettings(device: AVCaptureDevice, mode: AVCaptureDevice.FlashMode) { do { try device.lockForConfiguration() device.flashMode = mode @@ -950,17 +948,17 @@ open class SwiftyCamViewController: UIViewController { return } - let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) + let device = AVCaptureDevice.default(for: AVMediaType.video) // Check if device has a flash if (device?.hasTorch)! { do { try device?.lockForConfiguration() - if (device?.torchMode == AVCaptureTorchMode.on) { - device?.torchMode = AVCaptureTorchMode.off + if (device?.torchMode == AVCaptureDevice.TorchMode.on) { + device?.torchMode = AVCaptureDevice.TorchMode.off self.isCameraTorchOn = false } else { do { - try device?.setTorchModeOnWithLevel(1.0) + try device?.setTorchModeOn(level: 1.0) self.isCameraTorchOn = true } catch { print("[SwiftyCam]: \(error)") @@ -979,15 +977,21 @@ open class SwiftyCamViewController: UIViewController { guard allowBackgroundAudio == true else { return } - + guard audioEnabled == true else { return } do{ - try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, - with: [.duckOthers, .defaultToSpeaker]) - + if #available(iOS 10.0, *) { + try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [.mixWithOthers, .allowBluetooth, .allowAirPlay, .allowBluetoothA2DP]) + } else { + let options: [AVAudioSession.CategoryOptions] = [.mixWithOthers, .allowBluetooth] + let category = AVAudioSession.Category.playAndRecord + let selector = NSSelectorFromString("setCategory:withOptions:error:") + AVAudioSession.sharedInstance().perform(selector, with: category, with: options) + } + try AVAudioSession.sharedInstance().setActive(true) session.automaticallyConfiguresApplicationAudioSession = false } catch { @@ -995,6 +999,24 @@ open class SwiftyCamViewController: UIViewController { } } + + /// 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) + } + } } extension SwiftyCamViewController : SwiftyCamButtonDelegate { @@ -1037,26 +1059,27 @@ 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 + public func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) { + if let currentBackgroundRecordingID = backgroundRecordingID { + backgroundRecordingID = UIBackgroundTaskIdentifier.invalid - if currentBackgroundRecordingID != UIBackgroundTaskInvalid { - UIApplication.shared.endBackgroundTask(currentBackgroundRecordingID) - } - } - if error != nil { - print("[SwiftyCam]: Movie file finishing error: \(error)") - DispatchQueue.main.async { - self.cameraDelegate?.swiftyCam(self, didFailToRecordVideo: error) + if currentBackgroundRecordingID != UIBackgroundTaskIdentifier.invalid { + UIApplication.shared.endBackgroundTask(currentBackgroundRecordingID) } - } else { - //Call delegate function with the URL of the outputfile - DispatchQueue.main.async { - self.cameraDelegate?.swiftyCam(self, didFinishProcessVideoAt: outputFileURL) - } - } - } + } + + if let currentError = error { + print("[SwiftyCam]: Movie file finishing error: \(currentError)") + DispatchQueue.main.async { + self.cameraDelegate?.swiftyCam(self, didFailToRecordVideo: currentError) + } + } else { + //Call delegate function with the URL of the outputfile + DispatchQueue.main.async { + self.cameraDelegate?.swiftyCam(self, didFinishProcessVideoAt: outputFileURL) + } + } + } } // Mark: UIGestureRecognizer Declarations @@ -1067,11 +1090,11 @@ extension SwiftyCamViewController { @objc fileprivate func zoomGesture(pinch: UIPinchGestureRecognizer) { guard pinchToZoom == true && self.currentCamera == .rear else { - //ignore pinch + //ignore pinch return } do { - let captureDevice = AVCaptureDevice.devices().first as? AVCaptureDevice + let captureDevice = AVCaptureDevice.devices().first try captureDevice?.lockForConfiguration() zoomScale = min(maxZoomScale, max(1.0, min(beginZoomScale * pinch.scale, captureDevice!.activeFormat.videoMaxZoomFactor))) @@ -1113,7 +1136,7 @@ extension SwiftyCamViewController { device.focusMode = .autoFocus } device.exposurePointOfInterest = focusPoint - device.exposureMode = AVCaptureExposureMode.continuousAutoExposure + device.exposureMode = AVCaptureDevice.ExposureMode.continuousAutoExposure device.unlockForConfiguration() //Call delegate function and pass in the location of the touch @@ -1135,42 +1158,42 @@ extension SwiftyCamViewController { } switchCamera() } - + @objc private func panGesture(pan: UIPanGestureRecognizer) { - + guard swipeToZoom == true && self.currentCamera == .rear else { //ignore pan return } let currentTranslation = pan.translation(in: view).y let translationDifference = currentTranslation - previousPanTranslation - + do { - let captureDevice = AVCaptureDevice.devices().first as? AVCaptureDevice + let captureDevice = AVCaptureDevice.devices().first try captureDevice?.lockForConfiguration() - + let currentZoom = captureDevice?.videoZoomFactor ?? 0.0 - + if swipeToZoomInverted == true { zoomScale = min(maxZoomScale, max(1.0, min(currentZoom - (translationDifference / 75), captureDevice!.activeFormat.videoMaxZoomFactor))) } else { zoomScale = min(maxZoomScale, max(1.0, min(currentZoom + (translationDifference / 75), 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") } - + if pan.state == .ended || pan.state == .failed || pan.state == .cancelled { previousPanTranslation = 0.0 } else { @@ -1199,7 +1222,7 @@ extension SwiftyCamViewController { doubleTapGesture.numberOfTapsRequired = 2 doubleTapGesture.delegate = self previewLayer.addGestureRecognizer(doubleTapGesture) - + panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGesture(pan:))) panGesture.delegate = self previewLayer.addGestureRecognizer(panGesture) @@ -1220,7 +1243,3 @@ extension SwiftyCamViewController : UIGestureRecognizerDelegate { return true } } - - - - diff --git a/Source/SwiftyCamViewControllerDelegate.swift b/Source/SwiftyCamViewControllerDelegate.swift index 8102f06..d2e8ee8 100644 --- a/Source/SwiftyCamViewControllerDelegate.swift +++ b/Source/SwiftyCamViewControllerDelegate.swift @@ -15,6 +15,7 @@ import UIKit +import AVFoundation // MARK: Public Protocol Declaration @@ -22,6 +23,24 @@ import UIKit public protocol SwiftyCamViewControllerDelegate: class { + /** + SwiftyCamViewControllerDelegate function called when when SwiftyCamViewController session did start running. + Photos and video capture will be enabled. + + - Parameter swiftyCam: Current SwiftyCamViewController + */ + + func swiftyCamSessionDidStartRunning(_ swiftyCam: SwiftyCamViewController) + + /** + SwiftyCamViewControllerDelegate function called when when SwiftyCamViewController session did stops running. + Photos and video capture will be disabled. + + - Parameter swiftyCam: Current SwiftyCamViewController + */ + + func swiftyCamSessionDidStopRunning(_ swiftyCam: SwiftyCamViewController) + /** SwiftyCamViewControllerDelegate function called when the takePhoto() function is called. @@ -95,10 +114,34 @@ public protocol SwiftyCamViewControllerDelegate: class { */ func swiftyCam(_ swiftyCam: SwiftyCamViewController, didChangeZoomLevel zoom: CGFloat) + + /** + SwiftyCamViewControllerDelegate function called when when SwiftyCamViewController fails to confiture the session. + + - Parameter swiftyCam: Current SwiftyCamViewController + */ + + func swiftyCamDidFailToConfigure(_ swiftyCam: SwiftyCamViewController) + + /** + SwiftyCamViewControllerDelegate function called when when SwiftyCamViewController does not have access to camera or microphone. + + - Parameter swiftyCam: Current SwiftyCamViewController + */ + + func swiftyCamNotAuthorized(_ swiftyCam: SwiftyCamViewController) } public extension SwiftyCamViewControllerDelegate { + func swiftyCamSessionDidStopRunning(_ swiftyCam: SwiftyCamViewController) { + // Optional + } + + func swiftyCamSessionDidStartRunning(_ swiftyCam: SwiftyCamViewController) { + // Optional + } + func swiftyCam(_ swiftyCam: SwiftyCamViewController, didTake photo: UIImage) { // Optional } @@ -135,6 +178,14 @@ public extension SwiftyCamViewControllerDelegate { func swiftyCam(_ swiftyCam: SwiftyCamViewController, didChangeZoomLevel zoom: CGFloat) { // Optional } + + func swiftyCamDidFailToConfigure(_ swiftyCam: SwiftyCamViewController) { + // Optional + } + + func swiftyCamNotAuthorized(_ swiftyCam: SwiftyCamViewController) { + // Optional + } } diff --git a/SwiftyCam.podspec b/SwiftyCam.podspec index 0b1ecee..1fcb90d 100644 --- a/SwiftyCam.podspec +++ b/SwiftyCam.podspec @@ -12,13 +12,6 @@ Pod::Spec.new do |s| s.summary = 'A Simple, Snapchat inspired camera Framework written in Swift' s.ios.deployment_target = '8.0' - -# This description is used to generate tags and improve search results. -# * Think: What does it do? Why did you write it? What is the focus? -# * Try to keep it short, snappy and to the point. -# * Write the description between the DESC delimiters below. -# * Finally, don't worry about the indent, CocoaPods strips it! - s.description = <<-DESC A drop in Camera View Controller for capturing photos and videos from one AVSession. Written in Swift. DESC @@ -33,12 +26,4 @@ A drop in Camera View Controller for capturing photos and videos from one AVSess s.ios.deployment_target = '8.0' s.source_files = 'Source/**/*' - - # s.resource_bundles = { - # 'SwiftyCam' => ['SwiftyCam/Assets/*.png'] - # } - - # s.public_header_files = 'Pod/Classes/**/*.h' - # s.frameworks = 'UIKit', 'MapKit' - # s.dependency 'AFNetworking', '~> 2.3' end