Compare commits

...

4 Commits
master ... zoom

Author SHA1 Message Date
Ricardo Torrão c1d6fd5abf Saves still images with Zoom 2016-01-29 18:21:02 +00:00
Ricardo Torrão 5f94c9b898 Pinch to Zoom 2016-01-26 16:05:21 +00:00
Ricardo Torrão 93c9920a65 Merge branch 'zoom' of github.com:imaginary-cloud/CameraManager into zoom
# Conflicts:
#	camera.xcodeproj/project.xcworkspace/xcuserdata/nataliaterlecka.xcuserdatad/UserInterfaceState.xcuserstate
#	camera/CameraManager.swift
2016-01-26 10:07:54 +00:00
Natalia Terlecka eb4e77d2bb cameraIsReady property and addPreviewLayerToView competition block. 2015-12-24 09:52:39 +01:00
5 changed files with 145 additions and 49 deletions

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6214" systemVersion="14A314h" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6207"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<objects>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8191" systemVersion="14F27" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="bhK-VL-qY4">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="bhK-VL-qY4">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8154"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<scenes>

View File

@ -31,25 +31,25 @@ public enum CameraOutputQuality: Int {
}
/// Class for handling iDevices custom camera usage
public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate, UIGestureRecognizerDelegate {
// MARK: - Public properties
/// Capture session to customize camera settings.
public var captureSession: AVCaptureSession?
/// Property to determine if the manager should show the error for the user. If you want to show the errors yourself set this to false. If you want to add custom error UI set showErrorBlock property. Default value is false.
public var showErrorsToUsers = false
/// Property to determine if the manager should show the camera permission popup immediatly when it's needed or you want to show it manually. Default value is true. Be carful cause using the camera requires permission, if you set this value to false and don't ask manually you won't be able to use the camera.
public var showAccessPermissionPopupAutomatically = true
/// A block creating UI to present error message to the user. This can be customised to be presented on the Window root view controller, or to pass in the viewController which will present the UIAlertController, for example.
public var showErrorBlock:(erTitle: String, erMessage: String) -> Void = { (erTitle: String, erMessage: String) -> Void in
// var alertController = UIAlertController(title: erTitle, message: erMessage, preferredStyle: .Alert)
// alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: { (alertAction) -> Void in }))
//
//
// if let topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
// topController.presentViewController(alertController, animated: true, completion:nil)
// }
@ -58,6 +58,9 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
/// Property to determine if manager should write the resources to the phone library. Default value is true.
public var writeFilesToPhoneLibrary = true
/// Property to determine if manager allows zoom. Default value is true.
public var enableZoom = true
/// Property to determine if manager should follow device orientation. Default value is true.
public var shouldRespondToOrientationChanges = true {
didSet {
@ -68,14 +71,14 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
}
}
}
/// The Bool property to determine if the camera is ready to use.
public var cameraIsReady: Bool {
get {
return cameraIsSetup
}
}
/// The Bool property to determine if current device has front camera.
public var hasFrontCamera: Bool = {
let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo)
@ -87,7 +90,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
}
return false
}()
/// The Bool property to determine if current device has flash.
public var hasFlash: Bool = {
let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo)
@ -99,7 +102,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
}
return false
}()
/// Property to change camera device between front and back.
public var cameraDevice = CameraDevice.Back {
didSet {
@ -139,18 +142,19 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
if cameraIsSetup {
if cameraOutputMode != oldValue {
_setupOutputMode(cameraOutputMode, oldCameraOutputMode: oldValue)
_setupMaxZoom()
}
}
}
}
/// Property to check video recording duration when in progress
public var recordedDuration : CMTime { return movieOutput?.recordedDuration ?? kCMTimeZero }
/// Property to check video recording file size when in progress
public var recordedFileSize : Int64 { return movieOutput?.recordedFileSize ?? 0 }
// MARK: - Private properties
private weak var embedingView: UIView?
@ -162,16 +166,16 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo) as! [AVCaptureDevice]
return devices.filter{$0.position == .Front}.first
}()
private lazy var backCameraDevice: AVCaptureDevice? = {
let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo) as! [AVCaptureDevice]
return devices.filter{$0.position == .Back}.first
}()
private lazy var mic: AVCaptureDevice? = {
return AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeAudio)
}()
private var stillImageOutput: AVCaptureStillImageOutput?
private var movieOutput: AVCaptureMovieFileOutput?
private var previewLayer: AVCaptureVideoPreviewLayer?
@ -188,9 +192,14 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
} catch { }
}
return NSURL(string: tempPath)!
}()
}()
private var pinchToZoom: UIPinchGestureRecognizer?
private var zoomScale: CGFloat = 1.0
private var beginZoomScale: CGFloat = 1.0
private var maxZoomFactor: CGFloat = 1.0
// MARK: - CameraManager
/**
@ -199,7 +208,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
:param: view The view you want to add the preview layer to
:param: cameraOutputMode The mode you want capturesession to run image / video / video and microphone
:param: completition Optional completition block
:returns: Current state of the camera: Ready / AccessDenied / NoDeviceFound / NotDetermined.
*/
public func addPreviewLayerToView(view: UIView) -> CameraState {
@ -236,7 +245,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
/**
Asks the user for camera permissions. Only works if the permissions are not yet determined. Note that it'll also automaticaly ask about the microphone permissions if you selected VideoWithMic output.
:param: completition Completition block with the result of permission request
*/
public func askUserForCameraPermissions(completition: Bool -> Void) {
@ -340,7 +349,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
}
imageCompletition(UIImage(data: imageData), nil)
}
})
})
})
} else {
_show(NSLocalizedString("Capture session output mode video", comment:""), message: NSLocalizedString("I can't take any picture", comment:""))
@ -375,33 +384,33 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
/**
Current camera status.
:returns: Current state of the camera: Ready / AccessDenied / NoDeviceFound / NotDetermined
*/
public func currentCameraStatus() -> CameraState {
return _checkIfCameraIsAvailable()
}
/**
Change current flash mode to next value from available ones.
:returns: Current flash mode: Off / On / Auto
*/
public func changeFlashMode() -> CameraFlashMode {
flashMode = CameraFlashMode(rawValue: (flashMode.rawValue+1)%3)!
return flashMode
}
/**
Change current output quality mode to next value from available ones.
:returns: Current quality mode: Low / Medium / High
*/
public func changeQualityMode() -> CameraOutputQuality {
cameraOutputQuality = CameraOutputQuality(rawValue: (cameraOutputQuality.rawValue+1)%3)!
return cameraOutputQuality
}
// MARK: - AVCaptureFileOutputRecordingDelegate
public func captureOutput(captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAtURL fileURL: NSURL!, fromConnections connections: [AnyObject]!) {
@ -435,9 +444,48 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
}
}
}
// MARK: - UIGestureRecognizerDelegate
public func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.isKindOfClass(UIPinchGestureRecognizer) {
beginZoomScale = zoomScale;
}
return true
}
func _handlePinchGesture(pinchRecognizer: UIPinchGestureRecognizer) {
var allTouchesAreOnThePreviewLayer = true
let numTouches = pinchRecognizer.numberOfTouches();
for index in 0..<numTouches {
let location: CGPoint = pinchRecognizer.locationOfTouch(index, inView: embedingView)
let convertedLocation: CGPoint = (embedingView?.convertPoint(location, fromCoordinateSpace: (embedingView?.superview)!))!
if ((embedingView?.pointInside(convertedLocation, withEvent: nil)) == nil) {
allTouchesAreOnThePreviewLayer = false
break
}
}
if allTouchesAreOnThePreviewLayer {
_updateZoomScale(pinchRecognizer.scale)
_applyZoomScale()
}
}
// MARK: - CameraManager()
private func _updateZoomScale(scale: CGFloat) {
zoomScale = max(1.0, min(beginZoomScale * scale, maxZoomFactor))
}
private func _updateTorch(flashMode: CameraFlashMode) {
captureSession?.beginConfiguration()
let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo)
@ -458,7 +506,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
}
captureSession?.commitConfiguration()
}
private func _executeVideoCompletitionWithURL(url: NSURL?, error: NSError?) {
if let validCompletition = videoCompletition {
validCompletition(videoURL: url, error: error)
@ -473,7 +521,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
shouldReinitializeMovieOutput = shouldReinitializeMovieOutput || !connection.active
}
}
if shouldReinitializeMovieOutput {
movieOutput = AVCaptureMovieFileOutput()
movieOutput!.movieFragmentInterval = kCMTimeInvalid
@ -484,7 +532,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
}
return movieOutput!
}
private func _getStillImageOutput() -> AVCaptureStillImageOutput {
var shouldReinitializeStillImageOutput = stillImageOutput == nil
if !shouldReinitializeStillImageOutput {
@ -494,19 +542,22 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
}
if shouldReinitializeStillImageOutput {
stillImageOutput = AVCaptureStillImageOutput()
captureSession?.beginConfiguration()
captureSession?.addOutput(stillImageOutput)
captureSession?.commitConfiguration()
}
stillImageOutput!.connectionWithMediaType(AVMediaTypeVideo)?.videoScaleAndCropFactor = zoomScale
return stillImageOutput!
}
@objc private func _orientationChanged() {
var currentConnection: AVCaptureConnection?;
switch cameraOutputMode {
case .StillImage:
currentConnection = stillImageOutput?.connectionWithMediaType(AVMediaTypeVideo)
currentConnection = _getStillImageOutput().connectionWithMediaType(AVMediaTypeVideo)
case .VideoOnly, .VideoWithMic:
currentConnection = _getMovieOutput().connectionWithMediaType(AVMediaTypeVideo)
}
@ -547,12 +598,13 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
private func _setupCamera(completition: Void -> Void) {
captureSession = AVCaptureSession()
dispatch_async(sessionQueue, {
if let validCaptureSession = self.captureSession {
validCaptureSession.beginConfiguration()
validCaptureSession.sessionPreset = AVCaptureSessionPresetHigh
self._updateCameraDevice(self.cameraDevice)
self._setupPinchToZoom()
self._setupOutputs()
self._setupOutputMode(self.cameraOutputMode, oldCameraOutputMode: nil)
self._setupPreviewLayer()
@ -563,7 +615,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
self._startFollowingDeviceOrientation()
self.cameraIsSetup = true
self._orientationChanged()
completition()
}
})
@ -585,6 +637,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
private func _addPreviewLayerToView(view: UIView) {
embedingView = view
dispatch_async(dispatch_get_main_queue(), { () -> Void in
guard let _ = self.previewLayer else {
return
@ -614,15 +667,29 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
}
}
private func _setupMaxZoom() {
if cameraDevice == .Back {
if let maxZoom = backCameraDevice?.activeFormat.videoMaxZoomFactor {
maxZoomFactor = maxZoom
}
}
else {
if let maxZoom = frontCameraDevice?.activeFormat.videoMaxZoomFactor {
maxZoomFactor = maxZoom
}
}
}
private func _setupOutputMode(newCameraOutputMode: CameraOutputMode, oldCameraOutputMode: CameraOutputMode?) {
captureSession?.beginConfiguration()
if let cameraOutputToRemove = oldCameraOutputMode {
// remove current setting
switch cameraOutputToRemove {
case .StillImage:
if let validStillImageOutput = stillImageOutput {
captureSession?.removeOutput(validStillImageOutput)
_removePinchToZoom()
}
case .VideoOnly, .VideoWithMic:
if let validMovieOutput = movieOutput {
@ -633,7 +700,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
}
}
}
// configure new devices
switch newCameraOutputMode {
case .StillImage:
@ -642,10 +709,11 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
}
if let validStillImageOutput = stillImageOutput {
captureSession?.addOutput(validStillImageOutput)
_addPinchToZoom()
}
case .VideoOnly, .VideoWithMic:
captureSession?.addOutput(_getMovieOutput())
if newCameraOutputMode == .VideoWithMic {
if let validMic = _deviceInputFromDevice(mic) {
captureSession?.addInput(validMic)
@ -656,7 +724,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
_updateCameraQualityMode(cameraOutputQuality)
_orientationChanged()
}
private func _setupOutputs() {
if (stillImageOutput == nil) {
stillImageOutput = AVCaptureStillImageOutput()
@ -676,12 +744,12 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
previewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
}
}
private func _updateCameraDevice(deviceType: CameraDevice) {
if let validCaptureSession = captureSession {
validCaptureSession.beginConfiguration()
let inputs = validCaptureSession.inputs as! [AVCaptureInput]
for input in inputs {
if let deviceInput = input as? AVCaptureDeviceInput {
if deviceInput.device == backCameraDevice && cameraDevice == .Front {
@ -712,7 +780,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
validCaptureSession.commitConfiguration()
}
}
private func _updateFlasMode(flashMode: CameraFlashMode) {
captureSession?.beginConfiguration()
let devices = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo)
@ -763,7 +831,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
private func _removeMicInput() {
guard let inputs = captureSession?.inputs as? [AVCaptureInput] else { return }
for input in inputs {
if let deviceInput = input as? AVCaptureDeviceInput {
if deviceInput.device == mic {
@ -773,7 +841,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
}
}
}
private func _show(title: String, message: String) {
if showErrorsToUsers {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
@ -781,7 +849,7 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
})
}
}
private func _deviceInputFromDevice(device: AVCaptureDevice?) -> AVCaptureDeviceInput? {
guard let validDevice = device else { return nil }
do {
@ -791,6 +859,28 @@ public class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate {
return nil
}
}
private func _setupPinchToZoom() {
pinchToZoom = UIPinchGestureRecognizer(target: self, action: "_handlePinchGesture:")
pinchToZoom!.delegate = self
}
private func _addPinchToZoom() {
embedingView?.addGestureRecognizer(pinchToZoom!)
}
private func _removePinchToZoom() {
embedingView?.removeGestureRecognizer(pinchToZoom!)
}
private func _applyZoomScale() {
let affineTransform = CGAffineTransformMakeScale(zoomScale, zoomScale)
CATransaction.begin()
CATransaction.setAnimationDuration(0.025)
embedingView?.transform = affineTransform
CATransaction.commit()
}
deinit {
stopAndRemoveCaptureSession()

View File

@ -59,6 +59,11 @@
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
}
],
"info" : {