Created
March 22, 2019 06:09
-
-
Save wuaschtikus/f0134028f50d881e0784c00034b60923 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import AVFoundation | |
class Camera: NSObject { | |
var captureSession: AVCaptureSession? | |
var currentCameraPosition: CameraPosition? | |
var frontCamera: AVCaptureDevice? | |
var frontCameraInput: AVCaptureDeviceInput? | |
var photoOutput: AVCapturePhotoOutput? | |
var rearCamera: AVCaptureDevice? | |
var rearCameraInput: AVCaptureDeviceInput? | |
var previewLayer: AVCaptureVideoPreviewLayer? | |
var flashMode = AVCaptureDevice.FlashMode.off | |
var photoCaptureCompletionBlock: ((UIImage?, Error?) -> Void)? | |
} | |
extension Camera { | |
func prepare(completionHandler: @escaping (Error?) -> Void) { | |
func createCaptureSession() { | |
self.captureSession = AVCaptureSession() | |
} | |
func configureCaptureDevices() throws { | |
let session = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera], mediaType: AVMediaType.video, position: .unspecified) | |
let cameras = session.devices.compactMap { $0 } | |
guard !cameras.isEmpty else { throw CameraControllerError.noCamerasAvailable } | |
for camera in cameras { | |
if camera.position == .front { | |
self.frontCamera = camera | |
} | |
if camera.position == .back { | |
self.rearCamera = camera | |
try camera.lockForConfiguration() | |
camera.focusMode = .continuousAutoFocus | |
camera.unlockForConfiguration() | |
} | |
} | |
} | |
func configureDeviceInputs() throws { | |
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing } | |
if let rearCamera = self.rearCamera { | |
self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera) | |
if captureSession.canAddInput(self.rearCameraInput!) { captureSession.addInput(self.rearCameraInput!) } | |
self.currentCameraPosition = .rear | |
} | |
else if let frontCamera = self.frontCamera { | |
self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera) | |
if captureSession.canAddInput(self.frontCameraInput!) { captureSession.addInput(self.frontCameraInput!) } | |
else { throw CameraControllerError.inputsAreInvalid } | |
self.currentCameraPosition = .front | |
} | |
else { throw CameraControllerError.noCamerasAvailable } | |
} | |
func configurePhotoOutput() throws { | |
guard let captureSession = self.captureSession else { throw CameraControllerError.captureSessionIsMissing } | |
self.photoOutput = AVCapturePhotoOutput() | |
self.photoOutput!.setPreparedPhotoSettingsArray([AVCapturePhotoSettings(format: [AVVideoCodecKey : AVVideoCodecJPEG])], completionHandler: nil) | |
if captureSession.canAddOutput(self.photoOutput!) { captureSession.addOutput(self.photoOutput!) } | |
captureSession.startRunning() | |
} | |
DispatchQueue(label: "prepare").async { | |
do { | |
createCaptureSession() | |
try configureCaptureDevices() | |
try configureDeviceInputs() | |
try configurePhotoOutput() | |
} | |
catch { | |
DispatchQueue.main.async { | |
completionHandler(error) | |
} | |
return | |
} | |
DispatchQueue.main.async { | |
completionHandler(nil) | |
} | |
} | |
} | |
func displayPreview(on view: UIView) throws { | |
guard let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing } | |
self.previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) | |
self.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill | |
self.previewLayer?.connection?.videoOrientation = .portrait | |
view.layer.insertSublayer(self.previewLayer!, at: 0) | |
self.previewLayer?.frame = view.frame | |
} | |
func switchCameras() throws { | |
guard let currentCameraPosition = currentCameraPosition, let captureSession = self.captureSession, captureSession.isRunning else { throw CameraControllerError.captureSessionIsMissing } | |
captureSession.beginConfiguration() | |
func switchToFrontCamera() throws { | |
guard let rearCameraInput = self.rearCameraInput, captureSession.inputs.contains(rearCameraInput), | |
let frontCamera = self.frontCamera else { throw CameraControllerError.invalidOperation } | |
self.frontCameraInput = try AVCaptureDeviceInput(device: frontCamera) | |
captureSession.removeInput(rearCameraInput) | |
if captureSession.canAddInput(self.frontCameraInput!) { | |
captureSession.addInput(self.frontCameraInput!) | |
self.currentCameraPosition = .front | |
} | |
else { | |
throw CameraControllerError.invalidOperation | |
} | |
} | |
func switchToRearCamera() throws { | |
guard let frontCameraInput = self.frontCameraInput, captureSession.inputs.contains(frontCameraInput), | |
let rearCamera = self.rearCamera else { throw CameraControllerError.invalidOperation } | |
self.rearCameraInput = try AVCaptureDeviceInput(device: rearCamera) | |
captureSession.removeInput(frontCameraInput) | |
if captureSession.canAddInput(self.rearCameraInput!) { | |
captureSession.addInput(self.rearCameraInput!) | |
self.currentCameraPosition = .rear | |
} | |
else { throw CameraControllerError.invalidOperation } | |
} | |
switch currentCameraPosition { | |
case .front: | |
try switchToRearCamera() | |
case .rear: | |
try switchToFrontCamera() | |
} | |
captureSession.commitConfiguration() | |
} | |
func captureImage(completion: @escaping (UIImage?, Error?) -> Void) { | |
guard let captureSession = captureSession, captureSession.isRunning else { completion(nil, CameraControllerError.captureSessionIsMissing); return } | |
let settings = AVCapturePhotoSettings() | |
settings.flashMode = self.flashMode | |
self.photoOutput?.capturePhoto(with: settings, delegate: self) | |
self.photoCaptureCompletionBlock = completion | |
} | |
} | |
extension Camera: AVCapturePhotoCaptureDelegate { | |
public func photoOutput(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?, | |
resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Swift.Error?) { | |
if let error = error { self.photoCaptureCompletionBlock?(nil, error) } | |
else if let buffer = photoSampleBuffer, let data = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: buffer, previewPhotoSampleBuffer: nil), | |
let image = UIImage(data: data) { | |
self.photoCaptureCompletionBlock?(image, nil) | |
} | |
else { | |
self.photoCaptureCompletionBlock?(nil, CameraControllerError.unknown) | |
} | |
} | |
} | |
extension Camera { | |
enum CameraControllerError: Swift.Error { | |
case captureSessionAlreadyRunning | |
case captureSessionIsMissing | |
case inputsAreInvalid | |
case invalidOperation | |
case noCamerasAvailable | |
case unknown | |
} | |
public enum CameraPosition { | |
case front | |
case rear | |
} | |
} | |
// Camera Controller | |
import UIKit | |
import Photos | |
class CameraController: UIViewController { | |
@IBOutlet fileprivate var captureButton: UIButton! | |
///Displays a preview of the video output generated by the device's cameras. | |
@IBOutlet fileprivate var capturePreviewView: UIView! | |
///Allows the user to put the camera in photo mode. | |
@IBOutlet fileprivate var photoModeButton: UIButton! | |
@IBOutlet fileprivate var toggleCameraButton: UIButton! | |
@IBOutlet fileprivate var toggleFlashButton: UIButton! | |
///Allows the user to put the camera in video mode. | |
@IBOutlet fileprivate var videoModeButton: UIButton! | |
let camera = Camera() | |
override var prefersStatusBarHidden: Bool { return true } | |
} | |
extension CameraController { | |
override func viewDidLoad() { | |
func configureCameraController() { | |
camera.prepare {(error) in | |
if let error = error { | |
print(error) | |
} | |
try? self.camera.displayPreview(on: self.capturePreviewView) | |
} | |
} | |
func styleCaptureButton() { | |
captureButton.layer.borderColor = UIColor.black.cgColor | |
captureButton.layer.borderWidth = 2 | |
captureButton.layer.cornerRadius = min(captureButton.frame.width, captureButton.frame.height) / 2 | |
} | |
styleCaptureButton() | |
configureCameraController() | |
} | |
} | |
extension CameraController { | |
@IBAction func toggleFlash(_ sender: UIButton) { | |
if camera.flashMode == .on { | |
camera.flashMode = .off | |
toggleFlashButton.setImage(#imageLiteral(resourceName: "Flash Off Icon"), for: .normal) | |
} else { | |
camera.flashMode = .on | |
toggleFlashButton.setImage(#imageLiteral(resourceName: "Flash On Icon"), for: .normal) | |
} | |
} | |
@IBAction func switchCameras(_ sender: UIButton) { | |
do { | |
try camera.switchCameras() | |
} catch { | |
print(error) | |
} | |
switch camera.currentCameraPosition { | |
case .some(.front): toggleCameraButton.setImage(#imageLiteral(resourceName: "Front Camera Icon"), for: .normal) | |
case .some(.rear): toggleCameraButton.setImage(#imageLiteral(resourceName: "Rear Camera Icon"), for: .normal) | |
case .none: return | |
} | |
} | |
@IBAction func captureImage(_ sender: UIButton) { | |
camera.captureImage {(image, error) in | |
guard let image = image else { | |
print(error ?? "Image capture error") | |
return | |
} | |
try? PHPhotoLibrary.shared().performChangesAndWait { | |
PHAssetChangeRequest.creationRequestForAsset(from: image) | |
} | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
what were u using this code for?