diff --git a/ALCameraViewController.podspec b/ALCameraViewController.podspec index 6e3fdf82..6e160401 100644 --- a/ALCameraViewController.podspec +++ b/ALCameraViewController.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "ALCameraViewController" - spec.version = "1.3.1" + spec.version = "1.4.0" spec.summary = "A camera view controller with custom image picker and image cropping. Written in Swift." spec.source = { :git => "https://github.com/AlexLittlejohn/ALCameraViewController.git", :tag => spec.version.to_s } spec.requires_arc = true diff --git a/ALCameraViewController/Utilities/CameraShot.swift b/ALCameraViewController/Utilities/CameraShot.swift index 4142ce9a..acff1e6d 100644 --- a/ALCameraViewController/Utilities/CameraShot.swift +++ b/ALCameraViewController/Utilities/CameraShot.swift @@ -11,7 +11,7 @@ import AVFoundation public typealias CameraShotCompletion = (UIImage?) -> Void -public func takePhoto(_ stillImageOutput: AVCaptureStillImageOutput, videoOrientation: AVCaptureVideoOrientation, cropSize: CGSize, completion: @escaping CameraShotCompletion) { +public func takePhoto(_ stillImageOutput: AVCaptureStillImageOutput, videoOrientation: AVCaptureVideoOrientation, cameraPosition: AVCaptureDevicePosition, cropSize: CGSize, completion: @escaping CameraShotCompletion) { guard let videoConnection: AVCaptureConnection = stillImageOutput.connection(withMediaType: AVMediaTypeVideo) else { completion(nil) @@ -24,10 +24,32 @@ public func takePhoto(_ stillImageOutput: AVCaptureStillImageOutput, videoOrient guard let buffer = buffer, let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer), - let image = UIImage(data: imageData) else { + var image = UIImage(data: imageData) else { completion(nil) return } + + // flip the image to match the orientation of the preview + if cameraPosition == .front, let cgImage = image.cgImage { + switch image.imageOrientation { + case .leftMirrored: + image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .right) + case .left: + image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .rightMirrored) + case .rightMirrored: + image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .left) + case .right: + image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .leftMirrored) + case .up: + image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .upMirrored) + case .upMirrored: + image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .up) + case .down: + image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .downMirrored) + case .downMirrored: + image = UIImage(cgImage: cgImage, scale: image.scale, orientation: .down) + } + } completion(image) }) diff --git a/ALCameraViewController/Utilities/UIViewExtensions.swift b/ALCameraViewController/Utilities/UIViewExtensions.swift index c566ee81..59ed1ed7 100644 --- a/ALCameraViewController/Utilities/UIViewExtensions.swift +++ b/ALCameraViewController/Utilities/UIViewExtensions.swift @@ -3,7 +3,7 @@ import UIKit extension UIView { func autoRemoveConstraint(_ constraint : NSLayoutConstraint?) { if constraint != nil { - self.removeConstraint(constraint!) + removeConstraint(constraint!) } } } diff --git a/ALCameraViewController/Utilities/Utilities.swift b/ALCameraViewController/Utilities/Utilities.swift index d35977f7..45e19e85 100644 --- a/ALCameraViewController/Utilities/Utilities.swift +++ b/ALCameraViewController/Utilities/Utilities.swift @@ -9,8 +9,8 @@ import UIKit import AVFoundation -internal func radians(_ degrees: Double) -> Double { - return degrees / 180 * Double.pi +internal func radians(_ degrees: CGFloat) -> CGFloat { + return degrees / 180 * .pi } internal func localizedString(_ key: String) -> String { @@ -24,7 +24,7 @@ internal func localizedString(_ key: String) -> String { return NSLocalizedString(key, tableName: CameraGlobals.shared.stringsTable, bundle: bundle, comment: key) } -internal func currentRotation(_ oldOrientation: UIInterfaceOrientation, newOrientation: UIInterfaceOrientation) -> Double { +internal func currentRotation(_ oldOrientation: UIInterfaceOrientation, newOrientation: UIInterfaceOrientation) -> CGFloat { switch oldOrientation { case .portrait: switch newOrientation { diff --git a/ALCameraViewController/ViewController/CameraViewController.swift b/ALCameraViewController/ViewController/CameraViewController.swift index 6ba403bf..42d5cdcb 100644 --- a/ALCameraViewController/ViewController/CameraViewController.swift +++ b/ALCameraViewController/ViewController/CameraViewController.swift @@ -155,16 +155,21 @@ open class CameraViewController: UIViewController { view.translatesAutoresizingMaskIntoConstraints = false return view }() + + private let allowsLibraryAccess: Bool - public init(croppingEnabled: Bool, allowsLibraryAccess: Bool = true, completion: @escaping CameraViewCompletion) { + public init(croppingEnabled: Bool, allowsLibraryAccess: Bool = true, allowsSwapCameraOrientation: Bool = true, completion: @escaping CameraViewCompletion) { + self.allowsLibraryAccess = allowsLibraryAccess super.init(nibName: nil, bundle: nil) onCompletion = completion allowCropping = croppingEnabled cameraOverlay.isHidden = !allowCropping libraryButton.isEnabled = allowsLibraryAccess libraryButton.isHidden = !allowsLibraryAccess + swapButton.isEnabled = allowsSwapCameraOrientation + swapButton.isHidden = !allowsSwapCameraOrientation } - + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -386,8 +391,8 @@ open class CameraViewController: UIViewController { internal func rotate(actualInterfaceOrientation: UIInterfaceOrientation) { if lastInterfaceOrientation != nil { - let lastTransform = CGAffineTransform(rotationAngle: CGFloat(radians(currentRotation( - lastInterfaceOrientation!, newOrientation: actualInterfaceOrientation)))) + let lastTransform = CGAffineTransform(rotationAngle: radians(currentRotation( + lastInterfaceOrientation!, newOrientation: actualInterfaceOrientation))) setTransform(transform: lastTransform) } @@ -502,6 +507,7 @@ open class CameraViewController: UIViewController { let spinner = showSpinner() cameraView.preview.isHidden = true + if allowsLibraryAccess { _ = SingleImageSaver() .setImage(image) .onSuccess { [weak self] asset in @@ -515,8 +521,12 @@ open class CameraViewController: UIViewController { self?.hideSpinner(spinner) } .save() + } else { + layoutCameraResult(uiImage: image) + hideSpinner(spinner) + } } - + internal func close() { onCompletion?(nil, nil) onCompletion = nil @@ -558,13 +568,37 @@ open class CameraViewController: UIViewController { cameraView.swapCameraInput() flashButton.isHidden = cameraView.currentPosition == AVCaptureDevicePosition.front } - + + internal func layoutCameraResult(uiImage: UIImage) { + cameraView.stopSession() + startConfirmController(uiImage: uiImage) + toggleButtons(enabled: true) + } + internal func layoutCameraResult(asset: PHAsset) { cameraView.stopSession() startConfirmController(asset: asset) toggleButtons(enabled: true) } - + + private func startConfirmController(uiImage: UIImage) { + let confirmViewController = ConfirmViewController(image: uiImage, allowsCropping: allowCropping) + confirmViewController.onComplete = { [weak self] image, asset in + defer { + self?.dismiss(animated: true, completion: nil) + } + + guard let image = image else { + return + } + + self?.onCompletion?(image, asset) + self?.onCompletion = nil + } + confirmViewController.modalTransitionStyle = UIModalTransitionStyle.crossDissolve + present(confirmViewController, animated: true, completion: nil) + } + private func startConfirmController(asset: PHAsset) { let confirmViewController = ConfirmViewController(asset: asset, allowsCropping: allowCropping) confirmViewController.onComplete = { [weak self] image, asset in diff --git a/ALCameraViewController/ViewController/ConfirmViewController.swift b/ALCameraViewController/ViewController/ConfirmViewController.swift index 03e4613b..dba22b06 100644 --- a/ALCameraViewController/ViewController/ConfirmViewController.swift +++ b/ALCameraViewController/ViewController/ConfirmViewController.swift @@ -10,283 +10,356 @@ import UIKit import Photos public class ConfirmViewController: UIViewController, UIScrollViewDelegate { - - let imageView = UIImageView() - @IBOutlet weak var scrollView: UIScrollView! - @IBOutlet weak var cropOverlay: CropOverlay! - @IBOutlet weak var cancelButton: UIButton! - @IBOutlet weak var confirmButton: UIButton! - @IBOutlet weak var centeringView: UIView! - - var allowsCropping: Bool = false - var verticalPadding: CGFloat = 30 - var horizontalPadding: CGFloat = 30 - - public var onComplete: CameraViewCompletion? - - var asset: PHAsset! - - public init(asset: PHAsset, allowsCropping: Bool) { - self.allowsCropping = allowsCropping - self.asset = asset - super.init(nibName: "ConfirmViewController", bundle: CameraGlobals.shared.bundle) - } - - public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - public override var prefersStatusBarHidden: Bool { - return true - } - - public override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { - return UIStatusBarAnimation.slide - } - - public override func viewDidLoad() { - super.viewDidLoad() - - view.backgroundColor = UIColor.black - - scrollView.addSubview(imageView) - scrollView.delegate = self - scrollView.maximumZoomScale = 1 - - cropOverlay.isHidden = true - - guard let asset = asset else { - return - } - - let spinner = showSpinner() - - disable() - - _ = SingleImageFetcher() - .setAsset(asset) - .setTargetSize(largestPhotoSize()) - .onSuccess { [weak self] image in - self?.configureWithImage(image) - self?.hideSpinner(spinner) - self?.enable() - } - .onFailure { [weak self] error in - self?.hideSpinner(spinner) - } - .fetch() - } - - public override func viewWillLayoutSubviews() { - super.viewWillLayoutSubviews() - let scale = calculateMinimumScale(view.frame.size) - let frame = allowsCropping ? cropOverlay.frame : view.bounds - - scrollView.contentInset = calculateScrollViewInsets(frame) - scrollView.minimumZoomScale = scale - scrollView.zoomScale = scale - centerScrollViewContents() - centerImageViewOnRotate() - } - - public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) - - let scale = calculateMinimumScale(size) - var frame = view.bounds - - if allowsCropping { - frame = cropOverlay.frame - let centeringFrame = centeringView.frame - var origin: CGPoint - - if size.width > size.height { // landscape - let offset = (size.width - centeringFrame.height) - let expectedX = (centeringFrame.height/2 - frame.height/2) + offset - origin = CGPoint(x: expectedX, y: frame.origin.x) - } else { - let expectedY = (centeringFrame.width/2 - frame.width/2) - origin = CGPoint(x: frame.origin.y, y: expectedY) - } - - frame.origin = origin - } else { - frame.size = size - } - - let insets = calculateScrollViewInsets(frame) + + let imageView = UIImageView() + @IBOutlet weak var scrollView: UIScrollView! + @IBOutlet weak var cropOverlay: CropOverlay! + @IBOutlet weak var cancelButton: UIButton! + @IBOutlet weak var confirmButton: UIButton! + @IBOutlet weak var centeringView: UIView! + + var allowsCropping: Bool = false + var verticalPadding: CGFloat = 30 + var horizontalPadding: CGFloat = 30 + + public var onComplete: CameraViewCompletion? + + let asset: PHAsset? + let image: UIImage? + + public init(image: UIImage, allowsCropping: Bool) { + self.allowsCropping = allowsCropping + self.asset = nil + self.image = image + super.init(nibName: "ConfirmViewController", bundle: CameraGlobals.shared.bundle) + } + + public init(asset: PHAsset, allowsCropping: Bool) { + self.allowsCropping = allowsCropping + self.asset = asset + self.image = nil + super.init(nibName: "ConfirmViewController", bundle: CameraGlobals.shared.bundle) + } + + public required init?(coder aDecoder: NSCoder) { + asset = nil + image = nil + super.init(coder: aDecoder) + } + + public override var prefersStatusBarHidden: Bool { + return true + } + + public override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { + return UIStatusBarAnimation.slide + } + + public override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = UIColor.black + + scrollView.addSubview(imageView) + scrollView.delegate = self + scrollView.maximumZoomScale = 1 + + cropOverlay.isHidden = true + + let spinner = showSpinner() + + disable() + + if let asset = asset { + _ = SingleImageFetcher() + .setAsset(asset) + .setTargetSize(largestPhotoSize()) + .onSuccess { [weak self] image in + self?.configureWithImage(image) + self?.hideSpinner(spinner) + self?.enable() + } + .onFailure { [weak self] error in + self?.hideSpinner(spinner) + } + .fetch() + } else if let image = image { + configureWithImage(image) + hideSpinner(spinner) + enable() + } + } + + public override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + let scale = calculateMinimumScale(view.frame.size) + let frame = allowsCropping ? cropOverlay.frame : view.bounds + + scrollView.contentInset = calculateScrollViewInsets(frame) + scrollView.minimumZoomScale = scale + scrollView.zoomScale = scale + centerScrollViewContents() + centerImageViewOnRotate() + } + + public override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + + let scale = calculateMinimumScale(size) + var frame = view.bounds + + if allowsCropping { + frame = cropOverlay.frame + let centeringFrame = centeringView.frame + var origin: CGPoint + + if size.width > size.height { // landscape + let offset = (size.width - centeringFrame.height) + let expectedX = (centeringFrame.height/2 - frame.height/2) + offset + origin = CGPoint(x: expectedX, y: frame.origin.x) + } else { + let expectedY = (centeringFrame.width/2 - frame.width/2) + origin = CGPoint(x: frame.origin.y, y: expectedY) + } + + frame.origin = origin + } else { + frame.size = size + } + + let insets = calculateScrollViewInsets(frame) + + coordinator.animate(alongsideTransition: { [weak self] context in + self?.scrollView.contentInset = insets + self?.scrollView.minimumZoomScale = scale + self?.scrollView.zoomScale = scale + self?.centerScrollViewContents() + self?.centerImageViewOnRotate() + }, completion: nil) + } + + private func configureWithImage(_ image: UIImage) { + if allowsCropping { + cropOverlay.isHidden = false + } else { + cropOverlay.isHidden = true + } + + buttonActions() + + imageView.image = image + imageView.sizeToFit() + view.setNeedsLayout() + } + + private func calculateMinimumScale(_ size: CGSize) -> CGFloat { + var _size = size + + if allowsCropping { + _size = cropOverlay.frame.size + } + + guard let image = imageView.image else { + return 1 + } + + let scaleWidth = _size.width / image.size.width + let scaleHeight = _size.height / image.size.height + + var scale: CGFloat + + if allowsCropping { + scale = max(scaleWidth, scaleHeight) + } else { + scale = min(scaleWidth, scaleHeight) + } + + return scale + } + + private func calculateScrollViewInsets(_ frame: CGRect) -> UIEdgeInsets { + let bottom = view.frame.height - (frame.origin.y + frame.height) + let right = view.frame.width - (frame.origin.x + frame.width) + let insets = UIEdgeInsets(top: frame.origin.y, left: frame.origin.x, bottom: bottom, right: right) + return insets + } + + private func centerImageViewOnRotate() { + if allowsCropping { + let size = allowsCropping ? cropOverlay.frame.size : scrollView.frame.size + let scrollInsets = scrollView.contentInset + let imageSize = imageView.frame.size + var contentOffset = CGPoint(x: -scrollInsets.left, y: -scrollInsets.top) + contentOffset.x -= (size.width - imageSize.width) / 2 + contentOffset.y -= (size.height - imageSize.height) / 2 + scrollView.contentOffset = contentOffset + } + } + + private func centerScrollViewContents() { + let size = allowsCropping ? cropOverlay.frame.size : scrollView.frame.size + let imageSize = imageView.frame.size + var imageOrigin = CGPoint.zero + + if imageSize.width < size.width { + imageOrigin.x = (size.width - imageSize.width) / 2 + } + + if imageSize.height < size.height { + imageOrigin.y = (size.height - imageSize.height) / 2 + } + + imageView.frame.origin = imageOrigin + } + + private func buttonActions() { + confirmButton.action = { [weak self] in self?.confirmPhoto() } + cancelButton.action = { [weak self] in self?.cancel() } + } + + internal func cancel() { + onComplete?(nil, nil) + } + + internal func confirmPhoto() { + + guard let image = imageView.image else { + return + } + + disable() + + imageView.isHidden = true + + let spinner = showSpinner() + + if let asset = asset { + var fetcher = SingleImageFetcher() + .onSuccess { [weak self] image in + self?.onComplete?(image, self?.asset) + self?.hideSpinner(spinner) + self?.enable() + } + .onFailure { [weak self] error in + self?.hideSpinner(spinner) + self?.showNoImageScreen(error) + } + .setAsset(asset) + if allowsCropping { + let rect = normalizedRect(makeProportionalCropRect(), orientation: image.imageOrientation) + fetcher = fetcher.setCropRect(rect) + } + + fetcher = fetcher.fetch() + } else { + var newImage = image + + if allowsCropping { + let cropRect = makeProportionalCropRect() + let resizedCropRect = CGRect(x: (image.size.width) * cropRect.origin.x, + y: (image.size.height) * cropRect.origin.y, + width: (image.size.width * cropRect.width), + height: (image.size.height * cropRect.height)) + newImage = image.crop(rect: resizedCropRect) + } + + onComplete?(newImage, nil) + hideSpinner(spinner) + enable() + } + } + + public func viewForZooming(in scrollView: UIScrollView) -> UIView? { + return imageView + } + + public func scrollViewDidZoom(_ scrollView: UIScrollView) { + centerScrollViewContents() + } + + func showSpinner() -> UIActivityIndicatorView { + let spinner = UIActivityIndicatorView() + spinner.activityIndicatorViewStyle = .white + spinner.center = view.center + spinner.startAnimating() + + view.addSubview(spinner) + view.bringSubview(toFront: spinner) + + return spinner + } + + func hideSpinner(_ spinner: UIActivityIndicatorView) { + spinner.stopAnimating() + spinner.removeFromSuperview() + } + + func disable() { + confirmButton.isEnabled = false + } + + func enable() { + confirmButton.isEnabled = true + } + + func showNoImageScreen(_ error: NSError) { + let permissionsView = PermissionsView(frame: view.bounds) + + let desc = localizedString("error.cant-fetch-photo.description") + + permissionsView.configureInView(view, title: error.localizedDescription, description: desc, completion: { [weak self] in self?.cancel() }) + } + + private func makeProportionalCropRect() -> CGRect { + var cropRect = cropOverlay.frame + cropRect.origin.x += scrollView.contentOffset.x + cropRect.origin.y += scrollView.contentOffset.y + + let normalizedX = cropRect.origin.x / imageView.frame.width + let normalizedY = cropRect.origin.y / imageView.frame.height + + let normalizedWidth = cropRect.width / imageView.frame.width + let normalizedHeight = cropRect.height / imageView.frame.height + + return CGRect(x: normalizedX, y: normalizedY, width: normalizedWidth, height: normalizedHeight) + } + +} - coordinator.animate(alongsideTransition: { [weak self] context in - self?.scrollView.contentInset = insets - self?.scrollView.minimumZoomScale = scale - self?.scrollView.zoomScale = scale - self?.centerScrollViewContents() - self?.centerImageViewOnRotate() - }, completion: nil) - } - - private func configureWithImage(_ image: UIImage) { - if allowsCropping { - cropOverlay.isHidden = false - } else { - cropOverlay.isHidden = true - } - - buttonActions() - - imageView.image = image - imageView.sizeToFit() - view.setNeedsLayout() - } - - private func calculateMinimumScale(_ size: CGSize) -> CGFloat { - var _size = size - - if allowsCropping { - _size = cropOverlay.frame.size - } - - guard let image = imageView.image else { - return 1 - } - - let scaleWidth = _size.width / image.size.width - let scaleHeight = _size.height / image.size.height - - var scale: CGFloat - - if allowsCropping { - scale = max(scaleWidth, scaleHeight) - } else { - scale = min(scaleWidth, scaleHeight) - } - - return scale - } - - private func calculateScrollViewInsets(_ frame: CGRect) -> UIEdgeInsets { - let bottom = view.frame.height - (frame.origin.y + frame.height) - let right = view.frame.width - (frame.origin.x + frame.width) - let insets = UIEdgeInsets(top: frame.origin.y, left: frame.origin.x, bottom: bottom, right: right) - return insets - } - - private func centerImageViewOnRotate() { - if allowsCropping { - let size = allowsCropping ? cropOverlay.frame.size : scrollView.frame.size - let scrollInsets = scrollView.contentInset - let imageSize = imageView.frame.size - var contentOffset = CGPoint(x: -scrollInsets.left, y: -scrollInsets.top) - contentOffset.x -= (size.width - imageSize.width) / 2 - contentOffset.y -= (size.height - imageSize.height) / 2 - scrollView.contentOffset = contentOffset - } - } - - private func centerScrollViewContents() { - let size = allowsCropping ? cropOverlay.frame.size : scrollView.frame.size - let imageSize = imageView.frame.size - var imageOrigin = CGPoint.zero - - if imageSize.width < size.width { - imageOrigin.x = (size.width - imageSize.width) / 2 - } - - if imageSize.height < size.height { - imageOrigin.y = (size.height - imageSize.height) / 2 - } - - imageView.frame.origin = imageOrigin - } - - private func buttonActions() { - confirmButton.action = { [weak self] in self?.confirmPhoto() } - cancelButton.action = { [weak self] in self?.cancel() } - } - - internal func cancel() { - onComplete?(nil, nil) - } - - internal func confirmPhoto() { - - disable() - - imageView.isHidden = true - - let spinner = showSpinner() +extension UIImage { + func crop(rect: CGRect) -> UIImage { - var fetcher = SingleImageFetcher() - .onSuccess { [weak self] image in - self?.onComplete?(image, self?.asset) - self?.hideSpinner(spinner) - self?.enable() - } - .onFailure { [weak self] error in - self?.hideSpinner(spinner) - self?.showNoImageScreen(error) - } - .setAsset(asset) - - if allowsCropping { - - var cropRect = cropOverlay.frame - cropRect.origin.x += scrollView.contentOffset.x - cropRect.origin.y += scrollView.contentOffset.y - - let normalizedX = cropRect.origin.x / imageView.frame.width - let normalizedY = cropRect.origin.y / imageView.frame.height - - let normalizedWidth = cropRect.width / imageView.frame.width - let normalizedHeight = cropRect.height / imageView.frame.height - - let rect = normalizedRect(CGRect(x: normalizedX, y: normalizedY, width: normalizedWidth, height: normalizedHeight), orientation: imageView.image!.imageOrientation) - - fetcher = fetcher.setCropRect(rect) - } - - fetcher = fetcher.fetch() - } - - public func viewForZooming(in scrollView: UIScrollView) -> UIView? { - return imageView - } - - public func scrollViewDidZoom(_ scrollView: UIScrollView) { - centerScrollViewContents() - } - - func showSpinner() -> UIActivityIndicatorView { - let spinner = UIActivityIndicatorView() - spinner.activityIndicatorViewStyle = .white - spinner.center = view.center - spinner.startAnimating() - - view.addSubview(spinner) - view.bringSubview(toFront: spinner) - - return spinner - } - - func hideSpinner(_ spinner: UIActivityIndicatorView) { - spinner.stopAnimating() - spinner.removeFromSuperview() - } - - func disable() { - confirmButton.isEnabled = false - } - - func enable() { - confirmButton.isEnabled = true - } - - func showNoImageScreen(_ error: NSError) { - let permissionsView = PermissionsView(frame: view.bounds) - - let desc = localizedString("error.cant-fetch-photo.description") - - permissionsView.configureInView(view, title: error.localizedDescription, description: desc, completion: { [weak self] in self?.cancel() }) - } - + var rectTransform: CGAffineTransform + switch imageOrientation { + case .left: + rectTransform = CGAffineTransform(rotationAngle: radians(90)).translatedBy(x: 0, y: -size.height) + case .right: + rectTransform = CGAffineTransform(rotationAngle: radians(-90)).translatedBy(x: -size.width, y: 0) + case .down: + rectTransform = CGAffineTransform(rotationAngle: radians(-180)).translatedBy(x: -size.width, y: -size.height) + default: + rectTransform = CGAffineTransform.identity + } + + rectTransform = rectTransform.scaledBy(x: scale, y: scale) + + if let cropped = cgImage?.cropping(to: rect.applying(rectTransform)) { + return UIImage(cgImage: cropped, scale: scale, orientation: imageOrientation).fixOrientation() + } + + return self + } + + func fixOrientation() -> UIImage { + if imageOrientation == .up { + return self + } + + UIGraphicsBeginImageContextWithOptions(size, false, scale) + draw(in: CGRect(origin: .zero, size: size)) + let normalizedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext() ?? self + UIGraphicsEndImageContext() + + return normalizedImage + } } diff --git a/ALCameraViewController/Views/CameraView.swift b/ALCameraViewController/Views/CameraView.swift index 4dc3630c..67a30f4c 100644 --- a/ALCameraViewController/Views/CameraView.swift +++ b/ALCameraViewController/Views/CameraView.swift @@ -162,7 +162,7 @@ public class CameraView: UIView { let size = frame.size cameraQueue.sync { - takePhoto(output, videoOrientation: orientation, cropSize: size) { image in + takePhoto(output, videoOrientation: orientation, cameraPosition: device.position, cropSize: size) { image in DispatchQueue.main.async() { [weak self] in self?.isUserInteractionEnabled = true completion(image)