diff --git a/ALCameraViewController.podspec b/ALCameraViewController.podspec index 58fe93d3..1fa8c7f8 100644 --- a/ALCameraViewController.podspec +++ b/ALCameraViewController.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "ALCameraViewController" - spec.version = "3.0.2" + spec.version = "3.0.5" spec.summary = "A camera view controller with custom image picker and image cropping." spec.source = { :git => "https://github.com/AlexLittlejohn/ALCameraViewController.git", :tag => spec.version.to_s } spec.requires_arc = true @@ -10,5 +10,5 @@ Pod::Spec.new do |spec| spec.resources = ["ALCameraViewController/ViewController/ConfirmViewController.xib", "ALCameraViewController/CameraViewAssets.xcassets", "ALCameraViewController/CameraView.strings"] spec.homepage = "https://github.com/AlexLittlejohn/ALCameraViewController" spec.author = { "Alex Littlejohn" => "alexlittlejohn@me.com" } - spec.pod_target_xcconfig = { 'SWIFT_VERSION' => '4.0' } + spec.pod_target_xcconfig = { 'SWIFT_VERSION' => '3.2' } end diff --git a/ALCameraViewController.xcodeproj/project.pbxproj b/ALCameraViewController.xcodeproj/project.pbxproj index 720f06f0..641e8dde 100644 --- a/ALCameraViewController.xcodeproj/project.pbxproj +++ b/ALCameraViewController.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 1D4BC6A7CE46F52C503BE81A /* Pods_ALCameraViewController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EA3B6D5F07E9E32F0AF2A75 /* Pods_ALCameraViewController.framework */; }; 1F3C56291F701CA7009667E9 /* CroppingParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC96FA11F5B5166003E53F4 /* CroppingParameters.swift */; }; 7AC96FA21F5B5166003E53F4 /* CroppingParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC96FA11F5B5166003E53F4 /* CroppingParameters.swift */; }; 7C6AF41F1FB340CA006CB4ED /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7C6AF41E1FB340CA006CB4ED /* LaunchScreen.xib */; }; @@ -61,6 +62,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 30992D3B789B4F8CA8705055 /* Pods-ALCameraViewController.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ALCameraViewController.release.xcconfig"; path = "Pods/Target Support Files/Pods-ALCameraViewController/Pods-ALCameraViewController.release.xcconfig"; sourceTree = ""; }; + 5EA3B6D5F07E9E32F0AF2A75 /* Pods_ALCameraViewController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ALCameraViewController.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AC96FA11F5B5166003E53F4 /* CroppingParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CroppingParameters.swift; sourceTree = ""; }; 7C6AF41E1FB340CA006CB4ED /* LaunchScreen.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; C40665431C73A47C00EB9751 /* SingleImageSaver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleImageSaver.swift; sourceTree = ""; }; @@ -73,6 +76,7 @@ C4829FFF1CAEB16C00541D08 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C4D9BA441CA7224B004F70F7 /* PhotoLibraryAuthorizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoLibraryAuthorizer.swift; sourceTree = ""; }; C4D9BA461CA73163004F70F7 /* UIButtonExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIButtonExtensions.swift; sourceTree = ""; }; + D4AD3E148CB26CAF275AC0AC /* Pods-ALCameraViewController.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ALCameraViewController.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ALCameraViewController/Pods-ALCameraViewController.debug.xcconfig"; sourceTree = ""; }; EBF7829B1CB2C04300DE3E63 /* CameraViewControllerConstraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = CameraViewControllerConstraint.swift; sourceTree = ""; tabWidth = 4; }; EBFE097C1CAF1D1A00A8C637 /* UIViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewExtensions.swift; sourceTree = ""; }; FA52EE0A1B44129B00E16B6F /* ViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ViewController.xib; sourceTree = ""; }; @@ -108,12 +112,22 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 1D4BC6A7CE46F52C503BE81A /* Pods_ALCameraViewController.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 43256C8F415F4382CB1B01FE /* Pods */ = { + isa = PBXGroup; + children = ( + D4AD3E148CB26CAF275AC0AC /* Pods-ALCameraViewController.debug.xcconfig */, + 30992D3B789B4F8CA8705055 /* Pods-ALCameraViewController.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; C4829FFC1CAEB16C00541D08 /* CameraViewController */ = { isa = PBXGroup; children = ( @@ -123,6 +137,14 @@ path = CameraViewController; sourceTree = ""; }; + FA87334AA8FE91546E22FE97 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 5EA3B6D5F07E9E32F0AF2A75 /* Pods_ALCameraViewController.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; FAB50BEA1B413E41009905B9 /* ViewController */ = { isa = PBXGroup; children = ( @@ -171,6 +193,8 @@ FAF058411B31618D008E5592 /* ALCameraViewController */, C4829FFC1CAEB16C00541D08 /* CameraViewController */, FAF058401B31618D008E5592 /* Products */, + 43256C8F415F4382CB1B01FE /* Pods */, + FA87334AA8FE91546E22FE97 /* Frameworks */, ); sourceTree = ""; }; @@ -253,9 +277,12 @@ isa = PBXNativeTarget; buildConfigurationList = FAF0585E1B31618D008E5592 /* Build configuration list for PBXNativeTarget "ALCameraViewController" */; buildPhases = ( + B7EE613AF2C8F9F3F680AD43 /* [CP] Check Pods Manifest.lock */, FAF0583B1B31618D008E5592 /* Sources */, FAF0583C1B31618D008E5592 /* Frameworks */, FAF0583D1B31618D008E5592 /* Resources */, + 18CD297EA4D45E5795E3F66F /* [CP] Embed Pods Frameworks */, + 52854F814B04225D8FBC2683 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -283,7 +310,7 @@ }; FAF0583E1B31618D008E5592 = { CreatedOnToolsVersion = 6.3.2; - DevelopmentTeam = 2466624KEK; + DevelopmentTeam = 4L5389GUUE; LastSwiftMigration = 0900; ProvisioningStyle = Automatic; }; @@ -335,6 +362,60 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 18CD297EA4D45E5795E3F66F /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-ALCameraViewController/Pods-ALCameraViewController-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/ALCameraViewController/ALCameraViewController.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ALCameraViewController.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ALCameraViewController/Pods-ALCameraViewController-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 52854F814B04225D8FBC2683 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ALCameraViewController/Pods-ALCameraViewController-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + B7EE613AF2C8F9F3F680AD43 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ALCameraViewController-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ C4829FF61CAEB16C00541D08 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -545,10 +626,12 @@ }; FAF0585F1B31618D008E5592 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = D4AD3E148CB26CAF275AC0AC /* Pods-ALCameraViewController.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 2466624KEK; + DEVELOPMENT_TEAM = 4L5389GUUE; + ENABLE_BITCODE = NO; INFOPLIST_FILE = "Example/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -561,10 +644,12 @@ }; FAF058601B31618D008E5592 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 30992D3B789B4F8CA8705055 /* Pods-ALCameraViewController.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - DEVELOPMENT_TEAM = 2466624KEK; + DEVELOPMENT_TEAM = 4L5389GUUE; + ENABLE_BITCODE = NO; INFOPLIST_FILE = "Example/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; diff --git a/ALCameraViewController/Utilities/ALCameraViewController-Bridging-Header.h b/ALCameraViewController/Utilities/ALCameraViewController-Bridging-Header.h new file mode 100644 index 00000000..656f9fb6 --- /dev/null +++ b/ALCameraViewController/Utilities/ALCameraViewController-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +//#import diff --git a/ALCameraViewController/Utilities/CameraGlobals.swift b/ALCameraViewController/Utilities/CameraGlobals.swift index b09c7e89..3345ea2a 100644 --- a/ALCameraViewController/Utilities/CameraGlobals.swift +++ b/ALCameraViewController/Utilities/CameraGlobals.swift @@ -11,12 +11,12 @@ import AVFoundation internal let itemSpacing: CGFloat = 1 internal let columns: CGFloat = 4 -internal let thumbnailDimension = (UIScreen.main.bounds.width - ((columns * itemSpacing) - itemSpacing))/columns +internal let thumbnailDimension = (UIScreen.main.bounds.width - ((columns * itemSpacing) - itemSpacing)) / columns internal let scale = UIScreen.main.scale public class CameraGlobals { public static let shared = CameraGlobals() - + public var bundle = Bundle(for: CameraViewController.self) public var stringsTable = "CameraView" public var photoLibraryThumbnailSize = CGSize(width: thumbnailDimension, height: thumbnailDimension) diff --git a/ALCameraViewController/Utilities/CameraShot.swift b/ALCameraViewController/Utilities/CameraShot.swift index e4c62d6f..13083172 100644 --- a/ALCameraViewController/Utilities/CameraShot.swift +++ b/ALCameraViewController/Utilities/CameraShot.swift @@ -9,48 +9,62 @@ import UIKit import AVFoundation -public typealias CameraShotCompletion = (UIImage?) -> Void +public typealias CameraShotCompletion = (Data?, UIImage?, String?, String?) -> Void + +public func takePhoto(_ stillImageOutput: AVCaptureStillImageOutput, videoOrientation: AVCaptureVideoOrientation, cameraPosition: AVCaptureDevice.Position, cropSize _: CGSize, outputScale: CGFloat, completion: @escaping CameraShotCompletion) { -public func takePhoto(_ stillImageOutput: AVCaptureStillImageOutput, videoOrientation: AVCaptureVideoOrientation, cameraPosition: AVCaptureDevice.Position, cropSize: CGSize, completion: @escaping CameraShotCompletion) { - guard let videoConnection: AVCaptureConnection = stillImageOutput.connection(with: AVMediaType.video) else { - completion(nil) + completion(nil, nil, nil, nil) return } - + videoConnection.videoOrientation = videoOrientation - - stillImageOutput.captureStillImageAsynchronously(from: videoConnection, completionHandler: { buffer, error in - - guard let buffer = buffer, - let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer), - 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) + + if !stillImageOutput.isCapturingStillImage { + stillImageOutput.captureStillImageAsynchronously(from: videoConnection, completionHandler: { buffer, error in + + if let error = error { + completion(nil, nil, "Error in image capture: \(error.localizedDescription)", nil) + } + + guard let buffer = buffer, + let exifAttachments = CMGetAttachment(buffer, kCGImagePropertyExifDictionary, nil), + let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer), + var image = UIImage(data: imageData), + let cgImage = image.cgImage else { + DispatchQueue.main.async { + completion(nil, nil, "Error capture, NULL image buffer or NULL EXIF data", nil) + return + } + return + } + + // flip the image to match the orientation of the preview + // Half size is large for now + if cameraPosition == .front { + switch image.imageOrientation { + case .leftMirrored: + image = UIImage(cgImage: cgImage, scale: outputScale, orientation: .right) + case .left: + image = UIImage(cgImage: cgImage, scale: outputScale, orientation: .rightMirrored) + case .rightMirrored: + image = UIImage(cgImage: cgImage, scale: outputScale, orientation: .left) + case .right: + image = UIImage(cgImage: cgImage, scale: outputScale, orientation: .leftMirrored) + case .up: + image = UIImage(cgImage: cgImage, scale: outputScale, orientation: .upMirrored) + case .upMirrored: + image = UIImage(cgImage: cgImage, scale: outputScale, orientation: .up) + case .down: + image = UIImage(cgImage: cgImage, scale: outputScale, orientation: .downMirrored) + case .downMirrored: + image = UIImage(cgImage: cgImage, scale: outputScale, orientation: .down) + } + } else { + image = UIImage(cgImage: cgImage, scale: outputScale, orientation: image.imageOrientation) } - } - - completion(image) - }) + completion(imageData, image, nil, "EXIF \(exifAttachments)") + + }) + } } diff --git a/ALCameraViewController/Utilities/CroppingParameters.swift b/ALCameraViewController/Utilities/CroppingParameters.swift index 88f90acf..aa567093 100644 --- a/ALCameraViewController/Utilities/CroppingParameters.swift +++ b/ALCameraViewController/Utilities/CroppingParameters.swift @@ -29,7 +29,7 @@ public struct CroppingParameters { public init(isEnabled: Bool = false, allowResizing: Bool = true, allowMoving: Bool = true, - minimumSize: CGSize = CGSize(width: 60, height: 60)) { + minimumSize: CGSize = CGSize(width: 60, height: 60)) { self.isEnabled = isEnabled self.allowResizing = allowResizing diff --git a/ALCameraViewController/Utilities/ImageFetcher.swift b/ALCameraViewController/Utilities/ImageFetcher.swift index 09c4ea9f..444ce1bd 100644 --- a/ALCameraViewController/Utilities/ImageFetcher.swift +++ b/ALCameraViewController/Utilities/ImageFetcher.swift @@ -9,37 +9,37 @@ import UIKit import Photos -public typealias ImageFetcherSuccess = (PHFetchResult) -> () -public typealias ImageFetcherFailure = (NSError) -> () +public typealias ImageFetcherSuccess = (PHFetchResult) -> Void +public typealias ImageFetcherFailure = (NSError) -> Void -//extension PHFetchResult: Sequence { +// extension PHFetchResult: Sequence { // public func makeIterator() -> NSFastEnumerationIterator { // return NSFastEnumerationIterator(self) // } -//} +// } public class ImageFetcher { private var success: ImageFetcherSuccess? private var failure: ImageFetcherFailure? - + private var authRequested = false private let errorDomain = "com.zero.imageFetcher" - - let libraryQueue = DispatchQueue(label: "com.zero.ALCameraViewController.LibraryQueue"); - - public init() { } - + + let libraryQueue = DispatchQueue(label: "com.zero.ALCameraViewController.LibraryQueue") + + public init() {} + public func onSuccess(_ success: @escaping ImageFetcherSuccess) -> Self { self.success = success return self } - + public func onFailure(_ failure: @escaping ImageFetcherFailure) -> Self { self.failure = failure return self } - + public func fetch() -> Self { _ = PhotoLibraryAuthorizer { error in if error == nil { @@ -50,7 +50,7 @@ public class ImageFetcher { } return self } - + private func onAuthorized() { let options = PHFetchOptions() options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] diff --git a/ALCameraViewController/Utilities/PhotoLibraryAuthorizer.swift b/ALCameraViewController/Utilities/PhotoLibraryAuthorizer.swift index 43b10d67..acc63bf7 100644 --- a/ALCameraViewController/Utilities/PhotoLibraryAuthorizer.swift +++ b/ALCameraViewController/Utilities/PhotoLibraryAuthorizer.swift @@ -21,12 +21,12 @@ class PhotoLibraryAuthorizer { self.completion = completion handleAuthorization(status: PHPhotoLibrary.authorizationStatus()) } - + func onDeniedOrRestricted(completion: PhotoLibraryAuthorizerCompletion) { let error = errorWithKey("error.access-denied", domain: errorDomain) completion(error) } - + func handleAuthorization(status: PHAuthorizationStatus) { switch status { case .notDetermined: diff --git a/ALCameraViewController/Utilities/SingleImageFetcher.swift b/ALCameraViewController/Utilities/SingleImageFetcher.swift index dbbb9c6a..bb7473d3 100644 --- a/ALCameraViewController/Utilities/SingleImageFetcher.swift +++ b/ALCameraViewController/Utilities/SingleImageFetcher.swift @@ -14,41 +14,41 @@ public typealias SingleImageFetcherFailure = (NSError) -> Void public class SingleImageFetcher { private let errorDomain = "com.zero.singleImageSaver" - + private var success: SingleImageFetcherSuccess? private var failure: SingleImageFetcherFailure? - + private var asset: PHAsset? private var targetSize = PHImageManagerMaximumSize private var cropRect: CGRect? - - public init() { } - + + public init() {} + public func onSuccess(_ success: @escaping SingleImageFetcherSuccess) -> Self { self.success = success return self } - + public func onFailure(_ failure: @escaping SingleImageFetcherFailure) -> Self { self.failure = failure return self } - + public func setAsset(_ asset: PHAsset) -> Self { self.asset = asset return self } - + public func setTargetSize(_ targetSize: CGSize) -> Self { self.targetSize = targetSize return self } - + public func setCropRect(_ cropRect: CGRect) -> Self { self.cropRect = cropRect return self } - + public func fetch() -> Self { _ = PhotoLibraryAuthorizer { error in if error == nil { @@ -59,15 +59,15 @@ public class SingleImageFetcher { } return self } - + private func _fetch() { - + guard let asset = asset else { let error = errorWithKey("error.cant-fetch-photo", domain: errorDomain) failure?(error) return } - + let options = PHImageRequestOptions() options.deliveryMode = .highQualityFormat options.isNetworkAccessAllowed = true @@ -76,13 +76,13 @@ public class SingleImageFetcher { options.normalizedCropRect = cropRect options.resizeMode = .exact - + let targetWidth = floor(CGFloat(asset.pixelWidth) * cropRect.width) - let targetHeight = floor(CGFloat(asset.pixelHeight) * cropRect.height) - - targetSize = CGSize(width: targetWidth, height: targetHeight) + let targetHeight = floor(CGFloat(asset.pixelHeight) * cropRect.height) + + targetSize = CGSize(width: targetWidth, height: targetHeight) } - + PHImageManager.default().requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { image, _ in if let image = image { self.success?(image) diff --git a/ALCameraViewController/Utilities/SingleImageSaver.swift b/ALCameraViewController/Utilities/SingleImageSaver.swift index 652e1925..a1498092 100644 --- a/ALCameraViewController/Utilities/SingleImageSaver.swift +++ b/ALCameraViewController/Utilities/SingleImageSaver.swift @@ -14,31 +14,31 @@ public typealias SingleImageSaverFailure = (NSError) -> Void public class SingleImageSaver { private let errorDomain = "com.zero.singleImageSaver" - + private var success: SingleImageSaverSuccess? private var failure: SingleImageSaverFailure? - + private var image: UIImage? - - public init() { } - + + public init() {} + public func onSuccess(_ success: @escaping SingleImageSaverSuccess) -> Self { self.success = success return self } - + public func onFailure(_ failure: @escaping SingleImageSaverFailure) -> Self { self.failure = failure return self } - + public func setImage(_ image: UIImage) -> Self { self.image = image return self } - + public func save() -> Self { - + _ = PhotoLibraryAuthorizer { error in if error == nil { self._save() @@ -49,44 +49,44 @@ public class SingleImageSaver { return self } - + private func _save() { guard let image = image else { - self.invokeFailure() + invokeFailure() return } - + var assetIdentifier: PHObjectPlaceholder? - + PHPhotoLibrary.shared() .performChanges({ let request = PHAssetChangeRequest.creationRequestForAsset(from: image) assetIdentifier = request.placeholderForCreatedAsset - }) { finished, error in - + }) { finished, _ in + guard let assetIdentifier = assetIdentifier, finished else { self.invokeFailure() return } - + self.fetch(assetIdentifier) - } + } } - + private func fetch(_ assetIdentifier: PHObjectPlaceholder) { - + let assets = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentifier.localIdentifier], options: nil) - + DispatchQueue.main.async { guard let asset = assets.firstObject else { self.invokeFailure() return } - + self.success?(asset) } } - + private func invokeFailure() { let error = errorWithKey("error.cant-fetch-photo", domain: errorDomain) failure?(error) diff --git a/ALCameraViewController/Utilities/UIButtonExtensions.swift b/ALCameraViewController/Utilities/UIButtonExtensions.swift index c826b1de..6b75ec47 100644 --- a/ALCameraViewController/Utilities/UIButtonExtensions.swift +++ b/ALCameraViewController/Utilities/UIButtonExtensions.swift @@ -11,38 +11,38 @@ import UIKit typealias ButtonAction = () -> Void extension UIButton { - + private struct AssociatedKeys { static var ActionKey = "ActionKey" } - + private class ActionWrapper { let action: ButtonAction init(action: @escaping ButtonAction) { self.action = action } } - + var action: ButtonAction? { set(newValue) { removeTarget(self, action: #selector(performAction), for: .touchUpInside) - var wrapper: ActionWrapper? = nil + var wrapper: ActionWrapper? if let newValue = newValue { wrapper = ActionWrapper(action: newValue) addTarget(self, action: #selector(performAction), for: .touchUpInside) } - + objc_setAssociatedObject(self, &AssociatedKeys.ActionKey, wrapper, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } get { guard let wrapper = objc_getAssociatedObject(self, &AssociatedKeys.ActionKey) as? ActionWrapper else { return nil } - + return wrapper.action } } - + @objc func performAction() { guard let action = action else { return @@ -51,4 +51,3 @@ extension UIButton { action() } } - diff --git a/ALCameraViewController/Utilities/UIViewExtensions.swift b/ALCameraViewController/Utilities/UIViewExtensions.swift index 59ed1ed7..21b86786 100644 --- a/ALCameraViewController/Utilities/UIViewExtensions.swift +++ b/ALCameraViewController/Utilities/UIViewExtensions.swift @@ -1,9 +1,9 @@ import UIKit extension UIView { - func autoRemoveConstraint(_ constraint : NSLayoutConstraint?) { - if constraint != nil { - removeConstraint(constraint!) + func autoRemoveConstraint(_ constraint: NSLayoutConstraint?) { + if constraint != nil { + removeConstraint(constraint!) + } } - } } diff --git a/ALCameraViewController/Utilities/Utilities.swift b/ALCameraViewController/Utilities/Utilities.swift index 804ad970..9b2435b7 100644 --- a/ALCameraViewController/Utilities/Utilities.swift +++ b/ALCameraViewController/Utilities/Utilities.swift @@ -8,6 +8,7 @@ import UIKit import AVFoundation +import ImageIO internal func radians(_ degrees: CGFloat) -> CGFloat { return degrees / 180 * .pi @@ -26,31 +27,31 @@ internal func localizedString(_ key: String) -> String { internal func currentRotation(_ oldOrientation: UIInterfaceOrientation, newOrientation: UIInterfaceOrientation) -> CGFloat { switch oldOrientation { - case .portrait: - switch newOrientation { - case .landscapeLeft: return 90 - case .landscapeRight: return -90 - case .portraitUpsideDown: return 180 - default: return 0 - } - - case .landscapeLeft: - switch newOrientation { - case .portrait: return -90 - case .landscapeRight: return 180 - case .portraitUpsideDown: return 90 - default: return 0 - } - - case .landscapeRight: - switch newOrientation { - case .portrait: return 90 - case .landscapeLeft: return 180 - case .portraitUpsideDown: return -90 - default: return 0 - } - + case .portrait: + switch newOrientation { + case .landscapeLeft: return 90 + case .landscapeRight: return -90 + case .portraitUpsideDown: return 180 + default: return 0 + } + + case .landscapeLeft: + switch newOrientation { + case .portrait: return -90 + case .landscapeRight: return 180 + case .portraitUpsideDown: return 90 default: return 0 + } + + case .landscapeRight: + switch newOrientation { + case .portrait: return 90 + case .landscapeLeft: return 180 + case .portraitUpsideDown: return -90 + default: return 0 + } + + default: return 0 } } @@ -71,23 +72,24 @@ internal func errorWithKey(_ key: String, domain: String) -> NSError { internal func normalizedRect(_ rect: CGRect, orientation: UIImageOrientation) -> CGRect { let normalizedX = rect.origin.x let normalizedY = rect.origin.y - + let normalizedWidth = rect.width let normalizedHeight = rect.height - + var normalizedRect: CGRect - + switch orientation { case .up, .upMirrored: normalizedRect = CGRect(x: normalizedX, y: normalizedY, width: normalizedWidth, height: normalizedHeight) case .down, .downMirrored: - normalizedRect = CGRect(x: 1-normalizedX-normalizedWidth, y: 1-normalizedY-normalizedHeight, width: normalizedWidth, height: normalizedHeight) + normalizedRect = CGRect(x: 1 - normalizedX - normalizedWidth, y: 1 - normalizedY - normalizedHeight, width: normalizedWidth, height: normalizedHeight) case .left, .leftMirrored: - normalizedRect = CGRect(x: 1-normalizedY-normalizedHeight, y: normalizedX, width: normalizedHeight, height: normalizedWidth) + + normalizedRect = CGRect(x: 1 - normalizedY - normalizedHeight, y: normalizedX, width: normalizedHeight, height: normalizedWidth) case .right, .rightMirrored: - normalizedRect = CGRect(x: normalizedY, y: 1-normalizedX-normalizedWidth, width: normalizedHeight, height: normalizedWidth) + normalizedRect = CGRect(x: normalizedY, y: 1 - normalizedX - normalizedWidth, width: normalizedHeight, height: normalizedWidth) } - + return normalizedRect } @@ -105,19 +107,32 @@ internal func flashImage(_ mode: AVCaptureDevice.FlashMode) -> String { } struct ScreenSize { - static let SCREEN_WIDTH = UIScreen.main.bounds.size.width - static let SCREEN_HEIGHT = UIScreen.main.bounds.size.height - static let SCREEN_MAX_LENGTH = max(ScreenSize.SCREEN_WIDTH, ScreenSize.SCREEN_HEIGHT) + static let SCREEN_WIDTH = UIScreen.main.bounds.size.width + static let SCREEN_HEIGHT = UIScreen.main.bounds.size.height + static let SCREEN_MAX_LENGTH = max(ScreenSize.SCREEN_WIDTH, ScreenSize.SCREEN_HEIGHT) } struct DeviceConfig { - static let SCREEN_MULTIPLIER : CGFloat = { + static let SCREEN_MULTIPLIER: CGFloat = { + if UIDevice.current.userInterfaceIdiom == .phone { + switch ScreenSize.SCREEN_MAX_LENGTH { + case 568.0: return 1.5 + case 667.0: return 2.0 + case 736.0: return 4.0 + default: return 4.0 + } + } else { + return 1.0 + } + }() + + static let CLOSE_BUTTON_SPACING: CGFloat = { if UIDevice.current.userInterfaceIdiom == .phone { switch ScreenSize.SCREEN_MAX_LENGTH { - case 568.0: return 1.5 - case 667.0: return 2.0 - case 736.0: return 4.0 - default: return 1.0 + case 568.0: return -54 + case 667.0: return -66 + case 736.0: return -66 + default: return -66 } } else { return 1.0 diff --git a/ALCameraViewController/Utilities/VolumeControl.swift b/ALCameraViewController/Utilities/VolumeControl.swift index 36172f8a..d409536c 100644 --- a/ALCameraViewController/Utilities/VolumeControl.swift +++ b/ALCameraViewController/Utilities/VolumeControl.swift @@ -12,24 +12,24 @@ import MediaPlayer typealias VolumeChangeAction = (Float) -> Void public class VolumeControl { - + let changeKey = "AVSystemController_SystemVolumeDidChangeNotification" - + lazy var volumeView: MPVolumeView = { let view = MPVolumeView() view.frame = CGRect(x: 0, y: 0, width: 1, height: 1) view.alpha = 0.01 return view }() - + var onVolumeChange: VolumeChangeAction? - - init(view: UIView, onVolumeChange: VolumeChangeAction?) { + + init(view: UIView, enableAudio: Bool = true, onVolumeChange: VolumeChangeAction?) { self.onVolumeChange = onVolumeChange view.addSubview(volumeView) view.sendSubview(toBack: volumeView) - - try? AVAudioSession.sharedInstance().setActive(true) + + try? AVAudioSession.sharedInstance().setActive(enableAudio) NotificationCenter.default.addObserver(self, selector: #selector(volumeChanged), name: NSNotification.Name(rawValue: changeKey), object: nil) } diff --git a/ALCameraViewController/ViewController/CameraViewController.swift b/ALCameraViewController/ViewController/CameraViewController.swift index d9e07099..442eddb7 100644 --- a/ALCameraViewController/ViewController/CameraViewController.swift +++ b/ALCameraViewController/ViewController/CameraViewController.swift @@ -10,14 +10,14 @@ import UIKit import AVFoundation import Photos -public typealias CameraViewCompletion = (UIImage?, PHAsset?) -> Void +public typealias CameraViewCompletion = (Data?, UIImage?, PHAsset?, String?, String?) -> Void public extension CameraViewController { /// Provides an image picker wrapped inside a UINavigationController instance public class func imagePickerViewController(croppingParameters: CroppingParameters, completion: @escaping CameraViewCompletion) -> UINavigationController { let imagePicker = PhotoLibraryViewController() let navigationController = UINavigationController(rootViewController: imagePicker) - + navigationController.navigationBar.barTintColor = UIColor.black navigationController.navigationBar.barStyle = UIBarStyle.black navigationController.modalTransitionStyle = UIModalTransitionStyle.crossDissolve @@ -25,9 +25,9 @@ public extension CameraViewController { imagePicker.onSelectionComplete = { [weak imagePicker] asset in if let asset = asset { let confirmController = ConfirmViewController(asset: asset, croppingParameters: croppingParameters) - confirmController.onComplete = { [weak imagePicker] image, asset in + confirmController.onComplete = { [weak imagePicker] imageData, image, asset, errorData, exifData in if let image = image, let asset = asset { - completion(image, asset) + completion(imageData, image, asset, errorData, exifData) } else { imagePicker?.dismiss(animated: true, completion: nil) } @@ -35,68 +35,71 @@ public extension CameraViewController { confirmController.modalTransitionStyle = UIModalTransitionStyle.crossDissolve imagePicker?.present(confirmController, animated: true, completion: nil) } else { - completion(nil, nil) + completion(nil, nil, nil, nil, nil) } } - + return navigationController } } open class CameraViewController: UIViewController { - + var didUpdateViews = false var croppingParameters: CroppingParameters + var allowAudio = false var animationRunning = false let allowVolumeButtonCapture: Bool - - var lastInterfaceOrientation : UIInterfaceOrientation? + + var lastInterfaceOrientation: UIInterfaceOrientation? open var onCompletion: CameraViewCompletion? var volumeControl: VolumeControl? - + + var outputScale: CGFloat = 1.0 + var animationDuration: TimeInterval = 0.5 var animationSpring: CGFloat = 0.5 var rotateAnimation: UIViewAnimationOptions = .curveLinear - + var cameraButtonEdgeConstraint: NSLayoutConstraint? var cameraButtonGravityConstraint: NSLayoutConstraint? - + var closeButtonEdgeConstraint: NSLayoutConstraint? var closeButtonGravityConstraint: NSLayoutConstraint? - + var containerButtonsEdgeOneConstraint: NSLayoutConstraint? var containerButtonsEdgeTwoConstraint: NSLayoutConstraint? var containerButtonsGravityConstraint: NSLayoutConstraint? - + var swapButtonEdgeOneConstraint: NSLayoutConstraint? var swapButtonEdgeTwoConstraint: NSLayoutConstraint? var swapButtonGravityConstraint: NSLayoutConstraint? - + var libraryButtonEdgeOneConstraint: NSLayoutConstraint? var libraryButtonEdgeTwoConstraint: NSLayoutConstraint? var libraryButtonGravityConstraint: NSLayoutConstraint? - + var flashButtonEdgeConstraint: NSLayoutConstraint? var flashButtonGravityConstraint: NSLayoutConstraint? - + var cameraOverlayEdgeOneConstraint: NSLayoutConstraint? var cameraOverlayEdgeTwoConstraint: NSLayoutConstraint? var cameraOverlayWidthConstraint: NSLayoutConstraint? var cameraOverlayCenterConstraint: NSLayoutConstraint? - - let cameraView : CameraView = { + + let cameraView: CameraView = { let cameraView = CameraView() cameraView.translatesAutoresizingMaskIntoConstraints = false return cameraView }() - let cameraOverlay : CropOverlay = { + let cameraOverlay: CropOverlay = { let cameraOverlay = CropOverlay() cameraOverlay.translatesAutoresizingMaskIntoConstraints = false return cameraOverlay }() - - let cameraButton : UIButton = { + + let cameraButton: UIButton = { let button = UIButton(frame: CGRect(x: 0, y: 0, width: 64, height: 64)) button.translatesAutoresizingMaskIntoConstraints = false button.isEnabled = false @@ -110,8 +113,8 @@ open class CameraViewController: UIViewController { for: .highlighted) return button }() - - let closeButton : UIButton = { + + let closeButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.setImage(UIImage(named: "closeButton", @@ -120,8 +123,8 @@ open class CameraViewController: UIViewController { for: .normal) return button }() - - let swapButton : UIButton = { + + let swapButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.setImage(UIImage(named: "swapButton", @@ -130,8 +133,8 @@ open class CameraViewController: UIViewController { for: .normal) return button }() - - let libraryButton : UIButton = { + + let libraryButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.setImage(UIImage(named: "libraryButton", @@ -140,8 +143,8 @@ open class CameraViewController: UIViewController { for: .normal) return button }() - - let flashButton : UIButton = { + + let flashButton: UIButton = { let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44)) button.translatesAutoresizingMaskIntoConstraints = false button.setImage(UIImage(named: "flashAutoIcon", @@ -150,16 +153,16 @@ open class CameraViewController: UIViewController { for: .normal) return button }() - - let containerSwapLibraryButton : UIView = { + + let containerSwapLibraryButton: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false return view }() - - private let allowsLibraryAccess: Bool - - public init(croppingParameters: CroppingParameters = CroppingParameters(), + + private let allowsLibraryAccess: Bool + + public init(scaleFactor: CGFloat, croppingParameters: CroppingParameters = CroppingParameters(), allowsLibraryAccess: Bool = true, allowsSwapCameraOrientation: Bool = true, allowVolumeButtonCapture: Bool = true, @@ -169,27 +172,28 @@ open class CameraViewController: UIViewController { self.allowsLibraryAccess = allowsLibraryAccess self.allowVolumeButtonCapture = allowVolumeButtonCapture super.init(nibName: nil, bundle: nil) + outputScale = scaleFactor onCompletion = completion cameraOverlay.isHidden = !croppingParameters.isEnabled cameraOverlay.isUserInteractionEnabled = false libraryButton.isEnabled = allowsLibraryAccess libraryButton.isHidden = !allowsLibraryAccess - swapButton.isEnabled = allowsSwapCameraOrientation - swapButton.isHidden = !allowsSwapCameraOrientation + swapButton.isEnabled = allowsSwapCameraOrientation + swapButton.isHidden = !allowsSwapCameraOrientation } - - required public init?(coder aDecoder: NSCoder) { + + public required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } open override var prefersStatusBarHidden: Bool { return true } - + open override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { - return UIStatusBarAnimation.slide + return .slide } - + /** * Configure the background of the superview to black * and add the views on this superview. Then, request @@ -198,16 +202,18 @@ open class CameraViewController: UIViewController { open override func loadView() { super.loadView() view.backgroundColor = UIColor.black - [cameraView, + [ + cameraView, cameraOverlay, cameraButton, closeButton, flashButton, - containerSwapLibraryButton].forEach({ view.addSubview($0) }) + containerSwapLibraryButton, + ].forEach({ view.addSubview($0) }) [swapButton, libraryButton].forEach({ containerSwapLibraryButton.addSubview($0) }) view.setNeedsUpdateConstraints() } - + /** * Setup the constraints when the app is starting or rotating * the screen. @@ -216,27 +222,27 @@ open class CameraViewController: UIViewController { * Any other dynamic constraint are configurable when the * device is rotating, based on the device orientation. */ - override open func updateViewConstraints() { + open override func updateViewConstraints() { if !didUpdateViews { configCameraViewConstraints() didUpdateViews = true } - + let statusBarOrientation = UIApplication.shared.statusBarOrientation let portrait = statusBarOrientation.isPortrait - + configCameraButtonEdgeConstraint(statusBarOrientation) configCameraButtonGravityConstraint(portrait) - + removeCloseButtonConstraints() configCloseButtonEdgeConstraint(statusBarOrientation) configCloseButtonGravityConstraint(statusBarOrientation) - + removeContainerConstraints() configContainerEdgeConstraint(statusBarOrientation) configContainerGravityConstraint(statusBarOrientation) - + removeSwapButtonConstraints() configSwapButtonEdgeConstraint(statusBarOrientation) configSwapButtonGravityConstraint(portrait) @@ -244,22 +250,22 @@ open class CameraViewController: UIViewController { removeLibraryButtonConstraints() configLibraryEdgeButtonConstraint(statusBarOrientation) configLibraryGravityButtonConstraint(portrait) - + configFlashEdgeButtonConstraint(statusBarOrientation) configFlashGravityButtonConstraint(statusBarOrientation) - - let padding : CGFloat = portrait ? 16.0 : -16.0 + + let padding: CGFloat = portrait ? 16.0 : -16.0 removeCameraOverlayEdgesConstraints() configCameraOverlayEdgeOneContraint(portrait, padding: padding) configCameraOverlayEdgeTwoConstraint(portrait, padding: padding) configCameraOverlayWidthConstraint(portrait) configCameraOverlayCenterConstraint(portrait) - + rotate(actualInterfaceOrientation: statusBarOrientation) - + super.updateViewConstraints() } - + /** * Add observer to check when the camera has started, * enable the volume buttons to take the picture, @@ -274,7 +280,6 @@ open class CameraViewController: UIViewController { setupActions() checkPermissions() cameraView.configureFocus() - cameraView.configureZoom() } /** @@ -290,7 +295,7 @@ open class CameraViewController: UIViewController { setupVolumeControl() } } - + /** * Enable the button to take the picture when the * camera is ready. @@ -300,6 +305,7 @@ open class CameraViewController: UIViewController { if cameraView.session?.isRunning == true { notifyCameraReady() } + rotateCameraView() } open override func viewWillDisappear(_ animated: Bool) { @@ -311,7 +317,7 @@ open class CameraViewController: UIViewController { /** * This method will disable the rotation of the */ - override open func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) lastInterfaceOrientation = UIApplication.shared.statusBarOrientation @@ -321,13 +327,13 @@ open class CameraViewController: UIViewController { CATransaction.begin() CATransaction.setDisableActions(true) - coordinator.animate(alongsideTransition: { [weak self] animation in + coordinator.animate(alongsideTransition: { [weak self] _ in self?.view.setNeedsUpdateConstraints() - }, completion: { _ in - CATransaction.commit() + }, completion: { _ in + CATransaction.commit() }) } - + /** * Observer the camera status, when it is ready, * it calls the method cameraReady to enable the @@ -337,10 +343,10 @@ open class CameraViewController: UIViewController { NotificationCenter.default.addObserver( self, selector: #selector(notifyCameraReady), - name: NSNotification.Name.AVCaptureSessionDidStartRunning, + name: .AVCaptureSessionDidStartRunning, object: nil) } - + /** * Observer the device orientation to update the * orientation of CameraView. @@ -349,26 +355,26 @@ open class CameraViewController: UIViewController { NotificationCenter.default.addObserver( self, selector: #selector(rotateCameraView), - name: NSNotification.Name.UIDeviceOrientationDidChange, + name: .UIDeviceOrientationDidChange, object: nil) } - + @objc internal func notifyCameraReady() { cameraButton.isEnabled = true } - + /** * Attach the take of picture for any volume button. */ private func setupVolumeControl() { - volumeControl = VolumeControl(view: view) { [weak self] _ in + volumeControl = VolumeControl(view: view, enableAudio: allowAudio) { [weak self] _ in guard let enabled = self?.cameraButton.isEnabled, enabled else { return } self?.capturePhoto() } } - + /** * Configure the action for every button on this * layout. @@ -380,28 +386,33 @@ open class CameraViewController: UIViewController { closeButton.action = { [weak self] in self?.close() } flashButton.action = { [weak self] in self?.toggleFlash() } } - + /** * Toggle the buttons status, based on the actual * state of the camera. */ private func toggleButtons(enabled: Bool) { - [cameraButton, + [ + cameraButton, closeButton, swapButton, - libraryButton].forEach({ $0.isEnabled = enabled }) + libraryButton, + ].forEach({ $0.isEnabled = enabled }) } - + @objc func rotateCameraView() { - cameraView.rotatePreview() + cameraView.rotateCameraButtons(button: flashButton) + cameraView.rotateCameraButtons(button: swapButton) + cameraView.rotateCameraButtons(button: libraryButton) + cameraView.rotateCameraButtons(button: closeButton) } - + /** * This method will rotate the buttons based on * the last and actual orientation of the device. */ internal func rotate(actualInterfaceOrientation: UIInterfaceOrientation) { - + if lastInterfaceOrientation != nil { let lastTransform = CGAffineTransform(rotationAngle: radians(currentRotation( lastInterfaceOrientation!, newOrientation: actualInterfaceOrientation))) @@ -410,7 +421,7 @@ open class CameraViewController: UIViewController { let transform = CGAffineTransform(rotationAngle: 0) animationRunning = true - + /** * Dispatch delay to avoid any conflict between the CATransaction of rotation of the screen * and CATransaction of animation of buttons. @@ -420,17 +431,17 @@ open class CameraViewController: UIViewController { let spring = animationSpring let options = rotateAnimation - let time: DispatchTime = DispatchTime.now() + Double(1 * UInt64(NSEC_PER_SEC)/10) + let time: DispatchTime = DispatchTime.now() + Double(1 * UInt64(NSEC_PER_SEC) / 10) DispatchQueue.main.asyncAfter(deadline: time) { [weak self] in guard let _ = self else { return } - + CATransaction.begin() CATransaction.setDisableActions(false) CATransaction.commit() - + UIView.animate( withDuration: duration, delay: 0.1, @@ -438,21 +449,20 @@ open class CameraViewController: UIViewController { initialSpringVelocity: 0, options: options, animations: { [weak self] in - self?.setTransform(transform: transform) + self?.setTransform(transform: transform) }, completion: { [weak self] _ in self?.animationRunning = false }) - } } - + func setTransform(transform: CGAffineTransform) { closeButton.transform = transform swapButton.transform = transform libraryButton.transform = transform flashButton.transform = transform } - + /** * Validate the permissions of the camera and * library, if the user do not accept these @@ -470,7 +480,7 @@ open class CameraViewController: UIViewController { } } } - + /** * Generate the view of no permission. */ @@ -478,7 +488,7 @@ open class CameraViewController: UIViewController { let permissionsView = PermissionsView(frame: view.bounds) let title: String let desc: String - + if library { title = localizedString("permissions.library.title") desc = localizedString("permissions.library.description") @@ -486,10 +496,10 @@ open class CameraViewController: UIViewController { title = localizedString("permissions.title") desc = localizedString("permissions.description") } - + permissionsView.configureInView(view, title: title, description: desc, completion: { [weak self] in self?.close() }) } - + /** * This method will be called when the user * try to take the picture. @@ -502,50 +512,50 @@ open class CameraViewController: UIViewController { let connection = output.connection(with: AVMediaType.video) else { return } - + if connection.isEnabled { toggleButtons(enabled: false) - cameraView.capturePhoto { [weak self] image in - guard let image = image else { + cameraView.capturePhoto(scale: outputScale, completion: { [weak self] imageData, image, errorData, exifData in + guard let image = image, let imageData = imageData else { self?.toggleButtons(enabled: true) return } - self?.saveImage(image: image) - } + self?.saveImage(imageData: imageData, image: image, errorData: errorData, exifData: exifData) + }) } } - - internal func saveImage(image: UIImage) { + + internal func saveImage(imageData: Data, image: UIImage, errorData: String?, exifData: String?) { let spinner = showSpinner() cameraView.preview.isHidden = true - if allowsLibraryAccess { - _ = SingleImageSaver() - .setImage(image) - .onSuccess { [weak self] asset in - self?.layoutCameraResult(asset: asset) - self?.hideSpinner(spinner) - } - .onFailure { [weak self] error in - self?.toggleButtons(enabled: true) - self?.showNoPermissionsView(library: true) - self?.cameraView.preview.isHidden = false - self?.hideSpinner(spinner) - } - .save() - } else { - layoutCameraResult(uiImage: image) - hideSpinner(spinner) - } + if allowsLibraryAccess { + _ = SingleImageSaver() + .setImage(image) + .onSuccess { [weak self] asset in + self?.layoutCameraResult(asset: asset) + self?.hideSpinner(spinner) + } + .onFailure { [weak self] _ in + self?.toggleButtons(enabled: true) + self?.showNoPermissionsView(library: true) + self?.cameraView.preview.isHidden = false + self?.hideSpinner(spinner) + } + .save() + } else { + layoutCameraResult(imageData: imageData, uiImage: image, errorData: errorData, exifData: exifData) + hideSpinner(spinner) + } } - + internal func close() { - onCompletion?(nil, nil) + onCompletion?(nil, nil, nil, nil, nil) onCompletion = nil } - + internal func showLibrary() { - let imagePicker = CameraViewController.imagePickerViewController(croppingParameters: croppingParameters) { [weak self] image, asset in + let imagePicker = CameraViewController.imagePickerViewController(croppingParameters: croppingParameters) { [weak self] imageData, image, asset, errorData, exifData in defer { self?.dismiss(animated: true, completion: nil) } @@ -554,78 +564,79 @@ open class CameraViewController: UIViewController { return } - self?.onCompletion?(image, asset) + self?.onCompletion?(imageData, image, asset, errorData, exifData) } - + present(imagePicker, animated: true) { [weak self] in self?.cameraView.stopSession() } } - + internal func toggleFlash() { cameraView.cycleFlash() - + guard let device = cameraView.device else { return } - + let image = UIImage(named: flashImage(device.flashMode), in: CameraGlobals.shared.bundle, compatibleWith: nil) - + flashButton.setImage(image, for: .normal) } - + internal func swapCamera() { cameraView.swapCameraInput() flashButton.isHidden = cameraView.currentPosition == AVCaptureDevice.Position.front } - - internal func layoutCameraResult(uiImage: UIImage) { - cameraView.stopSession() - startConfirmController(uiImage: uiImage) - toggleButtons(enabled: true) - } - + + internal func layoutCameraResult(imageData: Data, uiImage: UIImage, errorData: String?, exifData: String?) { + cameraView.stopSession() + startConfirmController(imageData: imageData, uiImage: uiImage, errorData: errorData, exifData: exifData) + 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, croppingParameters: croppingParameters) - 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(imageData: Data, uiImage: UIImage, errorData: String?, exifData: String?) { + + let confirmViewController = ConfirmViewController(imageData: imageData, image: uiImage, errorData: errorData, exifData: exifData, croppingParameters: croppingParameters) + confirmViewController.onComplete = { [weak self] imageData, image, asset, errorData, exifData in + defer { + self?.dismiss(animated: true, completion: nil) + } + + guard let image = image, let imageData = imageData else { + return + } + + self?.onCompletion?(imageData, image, asset, errorData, exifData) + self?.onCompletion = nil + } + present(confirmViewController, animated: true, completion: nil) + } + private func startConfirmController(asset: PHAsset) { let confirmViewController = ConfirmViewController(asset: asset, croppingParameters: croppingParameters) - confirmViewController.onComplete = { [weak self] image, asset in + confirmViewController.onComplete = { [weak self] imageData, image, asset, errorData, exifData in defer { + self?.modalTransitionStyle = .partialCurl self?.dismiss(animated: true, completion: nil) } - guard let image = image, let asset = asset else { + guard let image = image, let asset = asset, let imageData = imageData else { return } - self?.onCompletion?(image, asset) + self?.onCompletion?(imageData, image, asset, errorData, exifData) self?.onCompletion = nil } - confirmViewController.modalTransitionStyle = UIModalTransitionStyle.crossDissolve + confirmViewController.modalTransitionStyle = .crossDissolve present(confirmViewController, animated: true, completion: nil) } @@ -634,16 +645,15 @@ open class CameraViewController: UIViewController { spinner.activityIndicatorViewStyle = .white spinner.center = view.center spinner.startAnimating() - + view.addSubview(spinner) view.bringSubview(toFront: spinner) - + return spinner } - + private func hideSpinner(_ spinner: UIActivityIndicatorView) { spinner.stopAnimating() spinner.removeFromSuperview() } - } diff --git a/ALCameraViewController/ViewController/CameraViewControllerConstraint.swift b/ALCameraViewController/ViewController/CameraViewControllerConstraint.swift index 4b485532..faa4122e 100644 --- a/ALCameraViewController/ViewController/CameraViewControllerConstraint.swift +++ b/ALCameraViewController/ViewController/CameraViewControllerConstraint.swift @@ -14,7 +14,7 @@ import AVFoundation * constraints for CameraViewController. */ extension CameraViewController { - + /** * To attach the view to the edges of the superview, it needs to be pinned on the sides of the self.view, based on the @@ -23,7 +23,7 @@ extension CameraViewController { * camera. */ func configCameraViewConstraints() { - [.left, .right, .top, .bottom].forEach({ + [.left, .right, .top].forEach({ view.addConstraint(NSLayoutConstraint( item: cameraView, attribute: $0, @@ -33,8 +33,17 @@ extension CameraViewController { multiplier: 1.0, constant: 0)) }) + + view.addConstraint(NSLayoutConstraint( + item: cameraView, + attribute: .bottom, + relatedBy: .equal, + toItem: view, + attribute: .bottom, + multiplier: 1.0, + constant: -35)) } - + /** * Add the constraints based on the device orientation, * this pin the button on the bottom part of the screen @@ -43,16 +52,16 @@ extension CameraViewController { */ func configCameraButtonEdgeConstraint(_ statusBarOrientation: UIInterfaceOrientation) { view.autoRemoveConstraint(cameraButtonEdgeConstraint) - - let attribute : NSLayoutAttribute = { + + let attribute: NSLayoutAttribute = { switch statusBarOrientation { case .portrait: return .bottom - case .landscapeRight: return .right - case .landscapeLeft: return .left - default: return .top + case .landscapeRight: return .top + case .landscapeLeft: return .top + default: return .bottom } }() - + cameraButtonEdgeConstraint = NSLayoutConstraint( item: cameraButton, attribute: attribute, @@ -60,10 +69,10 @@ extension CameraViewController { toItem: view, attribute: attribute, multiplier: 1.0, - constant: attribute == .right || attribute == .bottom ? -8 : 8) + constant: attribute == .right || attribute == .bottom ? -16 : -16) view.addConstraint(cameraButtonEdgeConstraint!) } - + /** * Add the constraints based on the device orientation, * centerX the button based on the width of screen. @@ -72,7 +81,7 @@ extension CameraViewController { */ func configCameraButtonGravityConstraint(_ portrait: Bool) { view.autoRemoveConstraint(cameraButtonGravityConstraint) - let attribute : NSLayoutAttribute = portrait ? .centerX : .centerY + let attribute: NSLayoutAttribute = portrait ? .centerX : .centerY cameraButtonGravityConstraint = NSLayoutConstraint( item: cameraButton, attribute: attribute, @@ -83,7 +92,7 @@ extension CameraViewController { constant: 0) view.addConstraint(cameraButtonGravityConstraint!) } - + /** * Remove the constraints of container. */ @@ -92,17 +101,17 @@ extension CameraViewController { view.autoRemoveConstraint(containerButtonsEdgeTwoConstraint) view.autoRemoveConstraint(containerButtonsGravityConstraint) } - + /** - * Configure the edges constraints of container that + * Configure the edges constraints of container that * handle the center position of SwapButton and * LibraryButton. */ - func configContainerEdgeConstraint(_ statusBarOrientation : UIInterfaceOrientation) { - - let attributeOne : NSLayoutAttribute - let attributeTwo : NSLayoutAttribute - + func configContainerEdgeConstraint(_ statusBarOrientation: UIInterfaceOrientation) { + + let attributeOne: NSLayoutAttribute + let attributeTwo: NSLayoutAttribute + switch statusBarOrientation { case .portrait: attributeOne = .left @@ -121,7 +130,7 @@ extension CameraViewController { attributeTwo = .left break } - + containerButtonsEdgeOneConstraint = NSLayoutConstraint( item: containerSwapLibraryButton, attribute: attributeOne, @@ -131,7 +140,7 @@ extension CameraViewController { multiplier: 1.0, constant: 0) view.addConstraint(containerButtonsEdgeOneConstraint!) - + containerButtonsEdgeTwoConstraint = NSLayoutConstraint( item: containerSwapLibraryButton, attribute: attributeTwo, @@ -141,15 +150,14 @@ extension CameraViewController { multiplier: 1.0, constant: 0) view.addConstraint(containerButtonsEdgeTwoConstraint!) - } - + /** * Configure the gravity of container, based on the * orientation of the device. */ - func configContainerGravityConstraint(_ statusBarOrientation : UIInterfaceOrientation) { - let attributeCenter : NSLayoutAttribute = statusBarOrientation.isPortrait ? .centerY : .centerX + func configContainerGravityConstraint(_ statusBarOrientation: UIInterfaceOrientation) { + let attributeCenter: NSLayoutAttribute = statusBarOrientation.isPortrait ? .centerY : .centerX containerButtonsGravityConstraint = NSLayoutConstraint( item: containerSwapLibraryButton, attribute: attributeCenter, @@ -160,7 +168,7 @@ extension CameraViewController { constant: 0) view.addConstraint(containerButtonsGravityConstraint!) } - + /** * Remove the SwapButton constraints to be updated when * the device was rotated. @@ -170,17 +178,17 @@ extension CameraViewController { view.autoRemoveConstraint(swapButtonEdgeTwoConstraint) view.autoRemoveConstraint(swapButtonGravityConstraint) } - + /** * If the device is portrait, pin the SwapButton on the * right side of the CameraButton. * If landscape, pin the SwapButton on the top of the * CameraButton. */ - func configSwapButtonEdgeConstraint(_ statusBarOrientation : UIInterfaceOrientation) { - - let attributeOne : NSLayoutAttribute - let attributeTwo : NSLayoutAttribute + func configSwapButtonEdgeConstraint(_ statusBarOrientation: UIInterfaceOrientation) { + + let attributeOne: NSLayoutAttribute + let attributeTwo: NSLayoutAttribute switch statusBarOrientation { case .portrait: @@ -200,7 +208,7 @@ extension CameraViewController { attributeTwo = .top break } - + swapButtonEdgeOneConstraint = NSLayoutConstraint( item: swapButton, attribute: attributeOne, @@ -210,7 +218,7 @@ extension CameraViewController { multiplier: 1.0, constant: 0) view.addConstraint(swapButtonEdgeOneConstraint!) - + swapButtonEdgeTwoConstraint = NSLayoutConstraint( item: swapButton, attribute: attributeTwo, @@ -220,9 +228,8 @@ extension CameraViewController { multiplier: 1.0, constant: 0) view.addConstraint(swapButtonEdgeTwoConstraint!) - } - + /** * Configure the center of SwapButton, based on the * axis center of CameraButton. @@ -238,21 +245,21 @@ extension CameraViewController { constant: -4.0 * DeviceConfig.SCREEN_MULTIPLIER) view.addConstraint(swapButtonGravityConstraint!) } - + func removeCloseButtonConstraints() { view.autoRemoveConstraint(closeButtonEdgeConstraint) view.autoRemoveConstraint(closeButtonGravityConstraint) } - + /** * Pin the close button to the left of the superview. */ - func configCloseButtonEdgeConstraint(_ statusBarOrientation : UIInterfaceOrientation) { - - let attribute : NSLayoutAttribute = { + func configCloseButtonEdgeConstraint(_ statusBarOrientation: UIInterfaceOrientation) { + + let attribute: NSLayoutAttribute = { switch statusBarOrientation { case .portrait: return .left - case .landscapeRight, .landscapeLeft: return .centerX + case .landscapeRight, .landscapeLeft: return .left default: return .right } }() @@ -261,13 +268,13 @@ extension CameraViewController { item: closeButton, attribute: attribute, relatedBy: .equal, - toItem: attribute != .centerX ? view : cameraButton, + toItem: attribute != .centerX ? cameraButton : cameraButton, attribute: attribute, multiplier: 1.0, - constant: attribute != .centerX ? 16 : 0) + constant: attribute != .centerX ? DeviceConfig.CLOSE_BUTTON_SPACING : 0) view.addConstraint(closeButtonEdgeConstraint!) } - + /** * Add the constraint for the CloseButton, based on * the device orientation. @@ -276,11 +283,11 @@ extension CameraViewController { * Else if landscape, pin this button on the Bottom * of superview. */ - func configCloseButtonGravityConstraint(_ statusBarOrientation : UIInterfaceOrientation) { - - let attribute : NSLayoutAttribute - let constant : CGFloat - + func configCloseButtonGravityConstraint(_ statusBarOrientation: UIInterfaceOrientation) { + + let attribute: NSLayoutAttribute + let constant: CGFloat + switch statusBarOrientation { case .portrait: attribute = .centerY @@ -299,7 +306,7 @@ extension CameraViewController { constant = 0.0 break } - + closeButtonGravityConstraint = NSLayoutConstraint( item: closeButton, attribute: attribute, @@ -308,10 +315,10 @@ extension CameraViewController { attribute: attribute, multiplier: 1.0, constant: constant) - + view.addConstraint(closeButtonGravityConstraint!) } - + /** * Remove the LibraryButton constraints to be updated when * the device was rotated. @@ -321,7 +328,7 @@ extension CameraViewController { view.autoRemoveConstraint(libraryButtonEdgeTwoConstraint) view.autoRemoveConstraint(libraryButtonGravityConstraint) } - + /** * Add the constraint of the LibraryButton, if the device * orientation is portrait, pin the right side of SwapButton @@ -329,11 +336,11 @@ extension CameraViewController { * If landscape, pin the bottom side of CameraButton on the * top side of LibraryButton. */ - func configLibraryEdgeButtonConstraint(_ statusBarOrientation : UIInterfaceOrientation) { + func configLibraryEdgeButtonConstraint(_ statusBarOrientation: UIInterfaceOrientation) { + + let attributeOne: NSLayoutAttribute + let attributeTwo: NSLayoutAttribute - let attributeOne : NSLayoutAttribute - let attributeTwo : NSLayoutAttribute - switch statusBarOrientation { case .portrait: attributeOne = .top @@ -352,7 +359,7 @@ extension CameraViewController { attributeTwo = .top break } - + libraryButtonEdgeOneConstraint = NSLayoutConstraint( item: libraryButton, attribute: attributeOne, @@ -362,7 +369,7 @@ extension CameraViewController { multiplier: 1.0, constant: 0) view.addConstraint(libraryButtonEdgeOneConstraint!) - + libraryButtonEdgeTwoConstraint = NSLayoutConstraint( item: libraryButton, attribute: attributeTwo, @@ -372,9 +379,8 @@ extension CameraViewController { multiplier: 1.0, constant: 0) view.addConstraint(libraryButtonEdgeTwoConstraint!) - } - + /** * Set the center gravity of the LibraryButton based * on the position of CameraButton. @@ -390,7 +396,7 @@ extension CameraViewController { constant: 4.0 * DeviceConfig.SCREEN_MULTIPLIER) view.addConstraint(libraryButtonGravityConstraint!) } - + /** * If the device orientation is portrait, pin the top of * FlashButton to the top side of superview. @@ -399,10 +405,10 @@ extension CameraViewController { */ func configFlashEdgeButtonConstraint(_ statusBarOrientation: UIInterfaceOrientation) { view.autoRemoveConstraint(flashButtonEdgeConstraint) - + let constraintRight = statusBarOrientation == .portrait || statusBarOrientation == .landscapeRight - let attribute : NSLayoutAttribute = constraintRight ? .top : .bottom - + let attribute: NSLayoutAttribute = constraintRight ? .top : .bottom + flashButtonEdgeConstraint = NSLayoutConstraint( item: flashButton, attribute: attribute, @@ -413,7 +419,7 @@ extension CameraViewController { constant: constraintRight ? 8 : -8) view.addConstraint(flashButtonEdgeConstraint!) } - + /** * If the device orientation is portrait, pin the right side of FlashButton to the right side of @@ -423,10 +429,10 @@ extension CameraViewController { */ func configFlashGravityButtonConstraint(_ statusBarOrientation: UIInterfaceOrientation) { view.autoRemoveConstraint(flashButtonGravityConstraint) - + let constraintRight = statusBarOrientation == .portrait || statusBarOrientation == .landscapeLeft - let attribute : NSLayoutAttribute = constraintRight ? .right : .left - + let attribute: NSLayoutAttribute = constraintRight ? .right : .left + flashButtonGravityConstraint = NSLayoutConstraint( item: flashButton, attribute: attribute, @@ -437,7 +443,7 @@ extension CameraViewController { constant: constraintRight ? -8 : 8) view.addConstraint(flashButtonGravityConstraint!) } - + /** * Used to create a perfect square for CameraOverlay. * This method will determinate the size of CameraOverlay, @@ -458,7 +464,7 @@ extension CameraViewController { constant: 0) view.addConstraint(cameraOverlayWidthConstraint!) } - + /** * This method will center the relative position of * CameraOverlay, based on the biggest size of the @@ -466,7 +472,7 @@ extension CameraViewController { */ func configCameraOverlayCenterConstraint(_ portrait: Bool) { view.autoRemoveConstraint(cameraOverlayCenterConstraint) - let attribute : NSLayoutAttribute = portrait ? .centerY : .centerX + let attribute: NSLayoutAttribute = portrait ? .centerY : .centerX cameraOverlayCenterConstraint = NSLayoutConstraint( item: cameraOverlay, attribute: attribute, @@ -477,7 +483,7 @@ extension CameraViewController { constant: 0) view.addConstraint(cameraOverlayCenterConstraint!) } - + /** * Remove the CameraOverlay constraints to be updated when * the device was rotated. @@ -486,7 +492,7 @@ extension CameraViewController { view.autoRemoveConstraint(cameraOverlayEdgeOneConstraint) view.autoRemoveConstraint(cameraOverlayEdgeTwoConstraint) } - + /** * It needs to get a determined smallest size of the screen to create the smallest size to be used on CameraOverlay. @@ -494,7 +500,7 @@ extension CameraViewController { the view will be pinned. */ func configCameraOverlayEdgeOneContraint(_ portrait: Bool, padding: CGFloat) { - let attribute : NSLayoutAttribute = portrait ? .left : .bottom + let attribute: NSLayoutAttribute = portrait ? .left : .bottom cameraOverlayEdgeOneConstraint = NSLayoutConstraint( item: cameraOverlay, attribute: attribute, @@ -505,7 +511,7 @@ extension CameraViewController { constant: padding) view.addConstraint(cameraOverlayEdgeOneConstraint!) } - + /** * It needs to get a determined smallest size of the screen to create the smallest size to be used on CameraOverlay. @@ -513,7 +519,7 @@ extension CameraViewController { the view will be pinned. */ func configCameraOverlayEdgeTwoConstraint(_ portrait: Bool, padding: CGFloat) { - let attributeTwo : NSLayoutAttribute = portrait ? .right : .top + let attributeTwo: NSLayoutAttribute = portrait ? .right : .top cameraOverlayEdgeTwoConstraint = NSLayoutConstraint( item: cameraOverlay, attribute: attributeTwo, @@ -524,5 +530,4 @@ extension CameraViewController { constant: -padding) view.addConstraint(cameraOverlayEdgeTwoConstraint!) } - } diff --git a/ALCameraViewController/ViewController/ConfirmViewController.swift b/ALCameraViewController/ViewController/ConfirmViewController.swift index d9eccd23..bb4e0201 100644 --- a/ALCameraViewController/ViewController/ConfirmViewController.swift +++ b/ALCameraViewController/ViewController/ConfirmViewController.swift @@ -10,14 +10,14 @@ 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! - + + 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 croppingParameters: CroppingParameters { didSet { cropOverlay.isResizable = croppingParameters.allowResizing @@ -25,347 +25,361 @@ public class ConfirmViewController: UIViewController, UIScrollViewDelegate { } } - var verticalPadding: CGFloat = 30 - var horizontalPadding: CGFloat = 30 - - public var onComplete: CameraViewCompletion? - - let asset: PHAsset? - let image: UIImage? - - public init(image: UIImage, croppingParameters: CroppingParameters) { - self.croppingParameters = croppingParameters - self.asset = nil - self.image = image - super.init(nibName: "ConfirmViewController", bundle: CameraGlobals.shared.bundle) - } - - public init(asset: PHAsset, croppingParameters: CroppingParameters) { - self.croppingParameters = croppingParameters - self.asset = asset - self.image = nil - super.init(nibName: "ConfirmViewController", bundle: CameraGlobals.shared.bundle) - } - + var verticalPadding: CGFloat = 30 + var horizontalPadding: CGFloat = 30 + + public var onComplete: CameraViewCompletion? + + let asset: PHAsset? + let image: UIImage? + let imageData: Data? + let errorData: String? + let exifData: String? + + public init(imageData: Data, image: UIImage, errorData: String?, exifData: String?, croppingParameters: CroppingParameters) { + self.croppingParameters = croppingParameters + asset = nil + self.imageData = imageData + self.image = image + self.errorData = errorData + self.exifData = exifData + super.init(nibName: "ConfirmViewController", bundle: CameraGlobals.shared.bundle) + } + + public init(asset: PHAsset, croppingParameters: CroppingParameters) { + self.croppingParameters = croppingParameters + self.asset = asset + image = nil + imageData = nil + errorData = nil + exifData = nil + super.init(nibName: "ConfirmViewController", bundle: CameraGlobals.shared.bundle) + } + public required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - 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 - + asset = nil + image = nil + imageData = nil + errorData = nil + exifData = nil + croppingParameters = CroppingParameters() + 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 cropOverlay.isResizable = croppingParameters.allowResizing cropOverlay.isMovable = croppingParameters.allowMoving cropOverlay.minimumSize = croppingParameters.minimumSize - 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 = croppingParameters.isEnabled ? 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 croppingParameters.isEnabled { - 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) { - cropOverlay.isHidden = !croppingParameters.isEnabled - - buttonActions() - - imageView.image = image - imageView.sizeToFit() - view.setNeedsLayout() - } - - private func calculateMinimumScale(_ size: CGSize) -> CGFloat { - var _size = size - - if croppingParameters.isEnabled { - _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 croppingParameters.isEnabled { - 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 croppingParameters.isEnabled { - let size = cropOverlay.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 = croppingParameters.isEnabled ? 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 croppingParameters.isEnabled { - let rect = normalizedRect(makeProportionalCropRect(), orientation: image.imageOrientation) - fetcher = fetcher.setCropRect(rect) - } - - fetcher = fetcher.fetch() - } else { - var newImage = image - - if croppingParameters.isEnabled { - 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 = CGRect(x: cropOverlay.frame.origin.x + cropOverlay.outterGap, - y: cropOverlay.frame.origin.y + cropOverlay.outterGap, - width: cropOverlay.frame.size.width - 2 * cropOverlay.outterGap, - height: cropOverlay.frame.size.height - 2 * cropOverlay.outterGap) - 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) - } - + 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] _ 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 = croppingParameters.isEnabled ? 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 croppingParameters.isEnabled { + 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] _ in + self?.scrollView.contentInset = insets + self?.scrollView.minimumZoomScale = scale + self?.scrollView.zoomScale = scale + self?.centerScrollViewContents() + self?.centerImageViewOnRotate() + }, completion: nil) + } + + private func configureWithImage(_ image: UIImage) { + cropOverlay.isHidden = !croppingParameters.isEnabled + + buttonActions() + + imageView.image = image + imageView.sizeToFit() + view.setNeedsLayout() + } + + private func calculateMinimumScale(_ size: CGSize) -> CGFloat { + var _size = size + + if croppingParameters.isEnabled { + _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 croppingParameters.isEnabled { + 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 croppingParameters.isEnabled { + let size = cropOverlay.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 = croppingParameters.isEnabled ? 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, nil, 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?(nil, image, self?.asset, nil, nil) + self?.hideSpinner(spinner) + self?.enable() + } + .onFailure { [weak self] error in + self?.hideSpinner(spinner) + self?.showNoImageScreen(error) + } + .setAsset(asset) + if croppingParameters.isEnabled { + let rect = normalizedRect(makeProportionalCropRect(), orientation: image.imageOrientation) + fetcher = fetcher.setCropRect(rect) + } + + fetcher = fetcher.fetch() + } else { + var newImage = image + + if croppingParameters.isEnabled { + 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?(imageData, newImage, nil, errorData, exifData) + hideSpinner(spinner) + enable() + } + } + + public func viewForZooming(in _: UIScrollView) -> UIView? { + return imageView + } + + public func scrollViewDidZoom(_: 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 = CGRect(x: cropOverlay.frame.origin.x + cropOverlay.outterGap, + y: cropOverlay.frame.origin.y + cropOverlay.outterGap, + width: cropOverlay.frame.size.width - 2 * cropOverlay.outterGap, + height: cropOverlay.frame.size.height - 2 * cropOverlay.outterGap) + 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) + } } extension UIImage { - func crop(rect: CGRect) -> UIImage { - - 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 - } + func crop(rect: CGRect) -> UIImage { + + 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/ViewController/ConfirmViewController.xib b/ALCameraViewController/ViewController/ConfirmViewController.xib index c3f3a9dc..01071a22 100644 --- a/ALCameraViewController/ViewController/ConfirmViewController.xib +++ b/ALCameraViewController/ViewController/ConfirmViewController.xib @@ -1,11 +1,12 @@ - - + + - - + + + @@ -22,185 +23,87 @@ - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/ALCameraViewController/ViewController/PhotoLibraryViewController.swift b/ALCameraViewController/ViewController/PhotoLibraryViewController.swift index 8bdc9cd6..5f09cf8f 100644 --- a/ALCameraViewController/ViewController/PhotoLibraryViewController.swift +++ b/ALCameraViewController/ViewController/PhotoLibraryViewController.swift @@ -16,97 +16,97 @@ internal let defaultItemSpacing: CGFloat = 1 public typealias PhotoLibraryViewSelectionComplete = (PHAsset?) -> Void public class PhotoLibraryViewController: UIViewController { - - internal var assets: PHFetchResult? = nil - + + internal var assets: PHFetchResult? + public var onSelectionComplete: PhotoLibraryViewSelectionComplete? - + private lazy var collectionView: UICollectionView = { let layout = UICollectionViewFlowLayout() - + layout.itemSize = CameraGlobals.shared.photoLibraryThumbnailSize layout.minimumInteritemSpacing = defaultItemSpacing layout.minimumLineSpacing = defaultItemSpacing layout.sectionInset = UIEdgeInsets.zero - + let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout) collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.backgroundColor = UIColor.clear return collectionView }() - + public override func viewDidLoad() { super.viewDidLoad() - + setNeedsStatusBarAppearanceUpdate() - + let buttonImage = UIImage(named: "libraryCancel", in: CameraGlobals.shared.bundle, compatibleWith: nil)?.withRenderingMode(UIImageRenderingMode.alwaysOriginal) - + navigationItem.leftBarButtonItem = UIBarButtonItem(image: buttonImage, style: UIBarButtonItemStyle.plain, target: self, action: #selector(dismissLibrary)) - + view.backgroundColor = UIColor(white: 0.2, alpha: 1) view.addSubview(collectionView) - + _ = ImageFetcher() .onFailure(onFailure) .onSuccess(onSuccess) .fetch() } - + public override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() collectionView.frame = view.bounds } - + public override var preferredStatusBarStyle: UIStatusBarStyle { return UIStatusBarStyle.lightContent } - + public func present(_ inViewController: UIViewController, animated: Bool) { let navigationController = UINavigationController(rootViewController: self) navigationController.navigationBar.barTintColor = UIColor.black navigationController.navigationBar.barStyle = UIBarStyle.black inViewController.present(navigationController, animated: animated, completion: nil) } - + @objc public func dismissLibrary() { onSelectionComplete?(nil) } - + private func onSuccess(_ photos: PHFetchResult) { assets = photos configureCollectionView() } - - private func onFailure(_ error: NSError) { + + private func onFailure(_: NSError) { let permissionsView = PermissionsView(frame: view.bounds) permissionsView.titleLabel.text = localizedString("permissions.library.title") permissionsView.descriptionLabel.text = localizedString("permissions.library.description") - + view.addSubview(permissionsView) } - + private func configureCollectionView() { collectionView.register(ImageCell.self, forCellWithReuseIdentifier: ImageCellIdentifier) collectionView.delegate = self collectionView.dataSource = self } - + internal func itemAtIndexPath(_ indexPath: IndexPath) -> PHAsset? { return assets?[(indexPath as NSIndexPath).row] } } // MARK: - UICollectionViewDataSource - -extension PhotoLibraryViewController : UICollectionViewDataSource { - public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { +extension PhotoLibraryViewController: UICollectionViewDataSource { + public func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int { return assets?.count ?? 0 } - @objc(collectionView:willDisplayCell:forItemAtIndexPath:) public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + @objc(collectionView:willDisplayCell:forItemAtIndexPath:) public func collectionView(_: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if cell is ImageCell { if let model = itemAtIndexPath(indexPath) { (cell as! ImageCell).configureWithModel(model) @@ -120,8 +120,8 @@ extension PhotoLibraryViewController : UICollectionViewDataSource { } // MARK: - UICollectionViewDelegate - -extension PhotoLibraryViewController : UICollectionViewDelegateFlowLayout { - public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { +extension PhotoLibraryViewController: UICollectionViewDelegateFlowLayout { + public func collectionView(_: UICollectionView, didSelectItemAt indexPath: IndexPath) { onSelectionComplete?(itemAtIndexPath(indexPath)) } } diff --git a/ALCameraViewController/Views/CameraView.swift b/ALCameraViewController/Views/CameraView.swift index b008924b..fe5f46de 100644 --- a/ALCameraViewController/Views/CameraView.swift +++ b/ALCameraViewController/Views/CameraView.swift @@ -10,25 +10,29 @@ import UIKit import AVFoundation public class CameraView: UIView { - + var session: AVCaptureSession! var input: AVCaptureDeviceInput! var device: AVCaptureDevice! var imageOutput: AVCaptureStillImageOutput! var preview: AVCaptureVideoPreviewLayer! - + let cameraQueue = DispatchQueue(label: "com.zero.ALCameraViewController.Queue") - + let focusView = CropOverlay(frame: CGRect(x: 0, y: 0, width: 80, height: 80)) - + + let minimumZoom: CGFloat = 1.0 + let maximumZoom: CGFloat = 3.0 + var lastZoomFactor: CGFloat = 1.0 + public var currentPosition = CameraGlobals.shared.defaultCameraPosition - + public func startSession() { session = AVCaptureSession() session.sessionPreset = AVCaptureSession.Preset.photo device = cameraWithPosition(position: currentPosition) - if let device = device , device.hasFlash { + if let device = device, device.hasFlash { do { try device.lockForConfiguration() device.flashMode = .auto @@ -39,7 +43,14 @@ public class CameraView: UIView { let outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG] do { - input = try AVCaptureDeviceInput(device: device) + if TARGET_OS_SIMULATOR != 0 { + input = nil + return + } + else { + input = try AVCaptureDeviceInput(device: device) + } + } catch let error as NSError { input = nil print("Error: \(error.localizedDescription)") @@ -57,18 +68,17 @@ public class CameraView: UIView { cameraQueue.sync { session.startRunning() - DispatchQueue.main.async() { [weak self] in + DispatchQueue.main.async { [weak self] in self?.createPreview() - self?.rotatePreview() } } } - + public func stopSession() { cameraQueue.sync { session?.stopRunning() preview?.removeFromSuperlayer() - + session = nil input = nil imageOutput = nil @@ -76,68 +86,66 @@ public class CameraView: UIView { device = nil } } - + public override func layoutSubviews() { super.layoutSubviews() preview?.frame = bounds + print("bounds updated: \(bounds)") } - + public func configureFocus() { - + if let gestureRecognizers = gestureRecognizers { gestureRecognizers.forEach({ removeGestureRecognizer($0) }) } - + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(focus(gesture:))) addGestureRecognizer(tapGesture) isUserInteractionEnabled = true addSubview(focusView) - + focusView.isHidden = true - + let lines = focusView.horizontalLines + focusView.verticalLines + focusView.outerLines - + lines.forEach { line in line.alpha = 0 } - } - public func configureZoom() { let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinch(gesture:))) addGestureRecognizer(pinchGesture) } - + @objc internal func focus(gesture: UITapGestureRecognizer) { let point = gesture.location(in: self) - + guard focusCamera(toPoint: point) else { return } - + focusView.isHidden = false focusView.center = point focusView.alpha = 0 focusView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2) - + bringSubview(toFront: focusView) - + UIView.animateKeyframes(withDuration: 1.5, delay: 0, options: UIViewKeyframeAnimationOptions(), animations: { - + UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.15, animations: { [weak self] in self?.focusView.alpha = 1 self?.focusView.transform = CGAffineTransform.identity }) - + UIView.addKeyframe(withRelativeStartTime: 0.80, relativeDuration: 0.20, animations: { [weak self] in self?.focusView.alpha = 0 self?.focusView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8) }) - - - }, completion: { [weak self] finished in - if finished { - self?.focusView.isHidden = true - } + + }, completion: { [weak self] finished in + if finished { + self?.focusView.isHidden = true + } }) } @@ -146,7 +154,7 @@ public class CameraView: UIView { // Return zoom value between the minimum and maximum zoom values func minMaxZoom(_ factor: CGFloat) -> CGFloat { - return min(max(factor, 1.0), device.activeFormat.videoMaxZoomFactor) + return min(min(max(factor, minimumZoom), maximumZoom), device.activeFormat.videoMaxZoomFactor) } func update(scale factor: CGFloat) { @@ -159,63 +167,63 @@ public class CameraView: UIView { } } - let velocity = gesture.velocity - let velocityFactor: CGFloat = 8.0 - let desiredZoomFactor = device.videoZoomFactor + atan2(velocity, velocityFactor) + let newScaleFactor = minMaxZoom(gesture.scale * lastZoomFactor) - let newScaleFactor = minMaxZoom(desiredZoomFactor) switch gesture.state { - case .began, .changed: - update(scale: newScaleFactor) - case _: - break + case .began: fallthrough + case .changed: update(scale: newScaleFactor) + case .ended: + lastZoomFactor = minMaxZoom(newScaleFactor) + update(scale: lastZoomFactor) + default: break } } - + private func createPreview() { - preview = AVCaptureVideoPreviewLayer(session: session) - preview.videoGravity = AVLayerVideoGravity.resizeAspectFill + preview.videoGravity = AVLayerVideoGravity.resizeAspect preview.frame = bounds - layer.addSublayer(preview) } - + private func cameraWithPosition(position: AVCaptureDevice.Position) -> AVCaptureDevice? { - let devices = AVCaptureDevice.devices(for: AVMediaType.video) + guard let devices = AVCaptureDevice.devices(for: AVMediaType.video) as? [AVCaptureDevice] else { + return nil + } return devices.filter { $0.position == position }.first } - - public func capturePhoto(completion: @escaping CameraShotCompletion) { + + public func capturePhoto(scale: CGFloat, completion: @escaping CameraShotCompletion) { isUserInteractionEnabled = false guard let output = imageOutput, let orientation = AVCaptureVideoOrientation(rawValue: UIDevice.current.orientation.rawValue) else { - completion(nil) + completion(nil, nil, nil, nil) return } + let outputScale = scale let size = frame.size cameraQueue.sync { - takePhoto(output, videoOrientation: orientation, cameraPosition: device.position, cropSize: size) { image in + takePhoto(output, videoOrientation: orientation, cameraPosition: device.position, cropSize: size, outputScale: outputScale) { imageData, image, errorData, exifData in DispatchQueue.main.async() { [weak self] in self?.isUserInteractionEnabled = true - completion(image) + completion(imageData, image, errorData, exifData) } } } } - + public func focusCamera(toPoint: CGPoint) -> Bool { - + guard let device = device, let preview = preview, device.isFocusModeSupported(.continuousAutoFocus) else { return false } - + do { try device.lockForConfiguration() } catch { return false } - + let focusPoint = preview.captureDevicePointConverted(fromLayerPoint: toPoint) device.focusPointOfInterest = focusPoint @@ -225,15 +233,15 @@ public class CameraView: UIView { device.exposureMode = .continuousAutoExposure device.unlockForConfiguration() - + return true } - + public func cycleFlash() { guard let device = device, device.hasFlash else { return } - + do { try device.lockForConfiguration() if device.flashMode == .on { @@ -244,18 +252,18 @@ public class CameraView: UIView { device.flashMode = .on } device.unlockForConfiguration() - } catch _ { } + } catch _ {} } public func swapCameraInput() { - + guard let session = session, let currentInput = input else { return } - + session.beginConfiguration() session.removeInput(currentInput) - + if currentInput.device.position == AVCaptureDevice.Position.back { currentPosition = AVCaptureDevice.Position.front device = cameraWithPosition(position: currentPosition) @@ -263,37 +271,42 @@ public class CameraView: UIView { currentPosition = AVCaptureDevice.Position.back device = cameraWithPosition(position: currentPosition) } - + guard let newInput = try? AVCaptureDeviceInput(device: device) else { return } - + input = newInput - + session.addInput(newInput) session.commitConfiguration() } - - public func rotatePreview() { - - guard preview != nil else { - return - } - switch UIApplication.shared.statusBarOrientation { + + public func rotateCameraButtons(button: UIButton) { + if let currentImage = button.currentImage { + switch UIDevice.current.orientation { case .portrait: - preview?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait - break + UIView.transition(with: button, duration: 0.5, options: .transitionFlipFromBottom, animations: { + button.setImage(UIImage(cgImage: (button.currentImage?.cgImage)!, scale: currentImage.scale, orientation: .up), for: .normal) + }, completion: nil) + break case .portraitUpsideDown: - preview?.connection?.videoOrientation = AVCaptureVideoOrientation.portraitUpsideDown - break + UIView.transition(with: button, duration: 0.5, options: .transitionFlipFromTop, animations: { + button.setImage(UIImage(cgImage: (button.currentImage?.cgImage)!, scale: currentImage.scale, orientation: .down), for: .normal) + }, completion: nil) + break case .landscapeRight: - preview?.connection?.videoOrientation = AVCaptureVideoOrientation.landscapeRight - break + UIView.transition(with: button, duration: 0.5, options: .transitionFlipFromLeft, animations: { + button.setImage(UIImage(cgImage: (button.currentImage?.cgImage)!, scale: currentImage.scale, orientation: .left), for: .normal) + }, completion: nil) + break case .landscapeLeft: - preview?.connection?.videoOrientation = AVCaptureVideoOrientation.landscapeLeft - break + UIView.transition(with: button, duration: 0.5, options: .transitionFlipFromRight, animations: { + button.setImage(UIImage(cgImage: (button.currentImage?.cgImage)!, scale: currentImage.scale, orientation: .right), for: .normal) + }, completion: nil) + break default: break + } } } - } diff --git a/ALCameraViewController/Views/CropOverlay.swift b/ALCameraViewController/Views/CropOverlay.swift index 862ece42..4ac287f3 100644 --- a/ALCameraViewController/Views/CropOverlay.swift +++ b/ALCameraViewController/Views/CropOverlay.swift @@ -13,7 +13,7 @@ internal class CropOverlay: UIView { var outerLines = [UIView]() var horizontalLines = [UIView]() var verticalLines = [UIView]() - + var topLeftCornerLines = [UIView]() var topRightCornerLines = [UIView]() var bottomLeftCornerLines = [UIView]() @@ -24,14 +24,14 @@ internal class CropOverlay: UIView { let cornerLineDepth: CGFloat = 3 let cornerLineWidth: CGFloat = 22.5 var cornerButtonWidth: CGFloat { - return self.cornerLineWidth * 2 + return cornerLineWidth * 2 } let lineWidth: CGFloat = 1 - let outterGapRatio: CGFloat = 1/3 + let outterGapRatio: CGFloat = 1 / 3 var outterGap: CGFloat { - return self.cornerButtonWidth * self.outterGapRatio + return cornerButtonWidth * outterGapRatio } var isResizable: Bool = false @@ -47,13 +47,13 @@ internal class CropOverlay: UIView { super.init(coder: aDecoder) createLines() } - + override func layoutSubviews() { - - for i in 0.. UIView { let line = UIView() line.backgroundColor = UIColor.white addSubview(line) return line } - - func createButton() -> UIButton { - let button = UIButton() - button.backgroundColor = UIColor.clear - - let dragGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(moveCropOverlay)) - button.addGestureRecognizer(dragGestureRecognizer) - - addSubview(button) - return button - } - - @objc func moveCropOverlay(gestureRecognizer: UIPanGestureRecognizer) { - if isResizable, let button = gestureRecognizer.view as? UIButton { - if gestureRecognizer.state == .began || gestureRecognizer.state == .changed { - let translation = gestureRecognizer.translation(in: self) - - var newFrame: CGRect - - switch button { - case cornerButtons[0]: // Top Left + + func createButton() -> UIButton { + let button = UIButton() + button.backgroundColor = UIColor.clear + + let dragGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(moveCropOverlay)) + button.addGestureRecognizer(dragGestureRecognizer) + + addSubview(button) + return button + } + + @objc func moveCropOverlay(gestureRecognizer: UIPanGestureRecognizer) { + if isResizable, let button = gestureRecognizer.view as? UIButton { + if gestureRecognizer.state == .began || gestureRecognizer.state == .changed { + let translation = gestureRecognizer.translation(in: self) + + var newFrame: CGRect + + switch button { + case cornerButtons[0]: // Top Left newFrame = CGRect(x: frame.origin.x + translation.x, y: frame.origin.y + translation.y, width: frame.size.width - translation.x, height: frame.size.height - translation.y) - case cornerButtons[1]: // Top Right - newFrame = CGRect(x: frame.origin.x, y: frame.origin.y + translation.y, width: frame.size.width + translation.x, height: frame.size.height - translation.y) - case cornerButtons[2]: // Bottom Left - newFrame = CGRect(x: frame.origin.x + translation.x, y: frame.origin.y, width: frame.size.width - translation.x, height: frame.size.height + translation.y) - case cornerButtons[3]: // Bottom Right - newFrame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width + translation.x, height: frame.size.height + translation.y) - default: - newFrame = CGRect.zero - } + case cornerButtons[1]: // Top Right + newFrame = CGRect(x: frame.origin.x, y: frame.origin.y + translation.y, width: frame.size.width + translation.x, height: frame.size.height - translation.y) + case cornerButtons[2]: // Bottom Left + newFrame = CGRect(x: frame.origin.x + translation.x, y: frame.origin.y, width: frame.size.width - translation.x, height: frame.size.height + translation.y) + case cornerButtons[3]: // Bottom Right + newFrame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width + translation.x, height: frame.size.height + translation.y) + default: + newFrame = CGRect.zero + } let minimumFrame = CGRect(x: newFrame.origin.x, y: newFrame.origin.y, width: max(newFrame.size.width, minimumSize.width + 2 * outterGap), height: max(newFrame.size.height, minimumSize.height + 2 * outterGap)) - frame = minimumFrame - layoutSubviews() - - gestureRecognizer.setTranslation(CGPoint.zero, in: self) - } - } else if isMovable { - if gestureRecognizer.state == .began || gestureRecognizer.state == .changed { - let translation = gestureRecognizer.translation(in: self) - - gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y) - gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self) - } - } - } + frame = minimumFrame + layoutSubviews() + + gestureRecognizer.setTranslation(CGPoint.zero, in: self) + } + } else if isMovable { + if gestureRecognizer.state == .began || gestureRecognizer.state == .changed { + let translation = gestureRecognizer.translation(in: self) + + gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y) + gestureRecognizer.setTranslation(CGPoint(x: 0, y: 0), in: self) + } + } + } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let view = super.hitTest(point, with: event) diff --git a/ALCameraViewController/Views/ImageCell.swift b/ALCameraViewController/Views/ImageCell.swift index b05053c0..b4d34bc2 100644 --- a/ALCameraViewController/Views/ImageCell.swift +++ b/ALCameraViewController/Views/ImageCell.swift @@ -10,8 +10,8 @@ import UIKit import Photos class ImageCell: UICollectionViewCell { - - let imageView : UIImageView = { + + let imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill imageView.layer.masksToBounds = true @@ -25,30 +25,30 @@ class ImageCell: UICollectionViewCell { super.init(frame: frame) contentView.addSubview(imageView) } - + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } - + override func layoutSubviews() { super.layoutSubviews() imageView.frame = bounds } - + override func prepareForReuse() { super.prepareForReuse() imageView.image = UIImage(named: "placeholder", in: CameraGlobals.shared.bundle, compatibleWith: nil) } - + func configureWithModel(_ model: PHAsset) { - + if tag != 0 { PHImageManager.default().cancelImageRequest(PHImageRequestID(tag)) } - - tag = Int(PHImageManager.default().requestImage(for: model, targetSize: contentView.bounds.size, contentMode: .aspectFill, options: nil) { image, info in + + tag = Int(PHImageManager.default().requestImage(for: model, targetSize: contentView.bounds.size, contentMode: .aspectFill, options: nil) { image, _ in self.imageView.image = image }) } diff --git a/ALCameraViewController/Views/PermissionsView.swift b/ALCameraViewController/Views/PermissionsView.swift index d814558b..5f44b778 100644 --- a/ALCameraViewController/Views/PermissionsView.swift +++ b/ALCameraViewController/Views/PermissionsView.swift @@ -9,115 +9,115 @@ import UIKit internal class PermissionsView: UIView { - + let iconView = UIImageView() let titleLabel = UILabel() let descriptionLabel = UILabel() let settingsButton = UIButton() - + let horizontalPadding: CGFloat = 50 let verticalPadding: CGFloat = 50 let verticalSpacing: CGFloat = 10 - + required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) commonInit() } - + override init(frame: CGRect) { super.init(frame: frame) commonInit() } - + func configureInView(_ view: UIView, title: String, description: String, completion: @escaping ButtonAction) { let closeButton = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44)) - + view.addSubview(self) addSubview(closeButton) - + titleLabel.text = title descriptionLabel.text = description - + closeButton.action = completion closeButton.setImage(UIImage(named: "retakeButton", in: CameraGlobals.shared.bundle, compatibleWith: nil), for: UIControlState()) closeButton.sizeToFit() - + let size = view.frame.size let closeSize = closeButton.frame.size let closeX = horizontalPadding let closeY = size.height - (closeSize.height + verticalPadding) - + closeButton.frame.origin = CGPoint(x: closeX, y: closeY) } - + func commonInit() { - + backgroundColor = UIColor(white: 0.2, alpha: 1) - + titleLabel.textColor = UIColor.white titleLabel.numberOfLines = 0 titleLabel.textAlignment = NSTextAlignment.center titleLabel.font = UIFont(name: "AppleSDGothicNeo-Light", size: 22) titleLabel.text = localizedString("permissions.title") - + descriptionLabel.textColor = UIColor.lightGray descriptionLabel.numberOfLines = 0 descriptionLabel.textAlignment = NSTextAlignment.center descriptionLabel.font = UIFont(name: "AppleSDGothicNeo-Regular", size: 16) descriptionLabel.text = localizedString("permissions.description") - + let icon = UIImage(named: "permissionsIcon", in: CameraGlobals.shared.bundle, compatibleWith: nil)! iconView.image = icon - + settingsButton.contentEdgeInsets = UIEdgeInsetsMake(6, 12, 6, 12) settingsButton.setTitle(localizedString("permissions.settings"), for: UIControlState()) settingsButton.setTitleColor(UIColor.white, for: UIControlState()) settingsButton.layer.cornerRadius = 4 settingsButton.titleLabel?.font = UIFont(name: "AppleSDGothicNeo-Regular", size: 14) - settingsButton.backgroundColor = UIColor(red: 52.0/255.0, green: 183.0/255.0, blue: 250.0/255.0, alpha: 1) + settingsButton.backgroundColor = UIColor(red: 52.0 / 255.0, green: 183.0 / 255.0, blue: 250.0 / 255.0, alpha: 1) settingsButton.addTarget(self, action: #selector(PermissionsView.openSettings), for: UIControlEvents.touchUpInside) - + addSubview(iconView) addSubview(titleLabel) addSubview(descriptionLabel) addSubview(settingsButton) } - + @objc func openSettings() { if let appSettings = URL(string: UIApplicationOpenSettingsURLString) { UIApplication.shared.openURL(appSettings) } } - + override func layoutSubviews() { super.layoutSubviews() - + let maxLabelWidth = frame.width - horizontalPadding * 2 - + let iconSize = iconView.image!.size let constrainedTextSize = CGSize(width: maxLabelWidth, height: CGFloat.greatestFiniteMagnitude) let titleSize = titleLabel.sizeThatFits(constrainedTextSize) let descriptionSize = descriptionLabel.sizeThatFits(constrainedTextSize) let settingsSize = settingsButton.sizeThatFits(constrainedTextSize) - - let iconX = frame.width/2 - iconSize.width/2 - let iconY: CGFloat = frame.height/2 - (iconSize.height + verticalSpacing + verticalSpacing + titleSize.height + verticalSpacing + descriptionSize.height)/2; - + + let iconX = frame.width / 2 - iconSize.width / 2 + let iconY: CGFloat = frame.height / 2 - (iconSize.height + verticalSpacing + verticalSpacing + titleSize.height + verticalSpacing + descriptionSize.height) / 2 + iconView.frame = CGRect(x: iconX, y: iconY, width: iconSize.width, height: iconSize.height) - - let titleX = frame.width/2 - titleSize.width/2 + + let titleX = frame.width / 2 - titleSize.width / 2 let titleY = iconY + iconSize.height + verticalSpacing + verticalSpacing - + titleLabel.frame = CGRect(x: titleX, y: titleY, width: titleSize.width, height: titleSize.height) - - let descriptionX = frame.width/2 - descriptionSize.width/2 + + let descriptionX = frame.width / 2 - descriptionSize.width / 2 let descriptionY = titleY + titleSize.height + verticalSpacing - + descriptionLabel.frame = CGRect(x: descriptionX, y: descriptionY, width: descriptionSize.width, height: descriptionSize.height) - - let settingsX = frame.width/2 - settingsSize.width/2 + + let settingsX = frame.width / 2 - settingsSize.width / 2 let settingsY = descriptionY + descriptionSize.height + verticalSpacing - + settingsButton.frame = CGRect(x: settingsX, y: settingsY, width: settingsSize.width, height: settingsSize.height) } } diff --git a/Example/AppDelegate.swift b/Example/AppDelegate.swift index c8de81bf..94eba29d 100644 --- a/Example/AppDelegate.swift +++ b/Example/AppDelegate.swift @@ -13,9 +13,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool { let viewController = ViewController(nibName: "ViewController", bundle: Bundle.main) - + window = UIWindow(frame: UIScreen.main.bounds) window!.rootViewController = viewController window!.makeKeyAndVisible() diff --git a/Example/Supporting Files/Info.plist b/Example/Supporting Files/Info.plist index 6d3c94b0..b5acfdc7 100644 --- a/Example/Supporting Files/Info.plist +++ b/Example/Supporting Files/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.10 + 3.0.4 CFBundleSignature ???? CFBundleVersion - 1 + 0 LSRequiresIPhoneOS NSCameraUsageDescription @@ -37,9 +37,6 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - UIInterfaceOrientationPortraitUpsideDown UISupportedInterfaceOrientations~ipad diff --git a/Example/ViewController.swift b/Example/ViewController.swift index e4c6aa1b..71568edb 100644 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -19,39 +19,39 @@ class ViewController: UIViewController { var croppingParameters: CroppingParameters { return CroppingParameters(isEnabled: croppingEnabled, allowResizing: allowResizing, allowMoving: allowMoving, minimumSize: minimumSize) } - + @IBOutlet weak var imageView: UIImageView! @IBOutlet weak var croppingParametersView: UIView! @IBOutlet weak var minimumSizeLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() - - self.imageView.contentMode = .scaleAspectFit + + imageView.contentMode = .scaleAspectFit } - - @IBAction func openCamera(_ sender: Any) { - let cameraViewController = CameraViewController(croppingParameters: croppingParameters, allowsLibraryAccess: libraryEnabled) { [weak self] image, asset in + + @IBAction func openCamera(_: AnyObject) { + let cameraViewController = CameraViewController(scaleFactor: 3.0, croppingParameters: croppingParameters, allowsLibraryAccess: libraryEnabled, allowsSwapCameraOrientation: true, allowVolumeButtonCapture: false) { [weak self] _, image, _, _, _ in self?.imageView.image = image self?.dismiss(animated: true, completion: nil) } - + present(cameraViewController, animated: true, completion: nil) } - - @IBAction func openLibrary(_ sender: Any) { - let libraryViewController = CameraViewController.imagePickerViewController(croppingParameters: croppingParameters) { [weak self] image, asset in + + @IBAction func openLibrary(_: AnyObject) { + let libraryViewController = CameraViewController.imagePickerViewController(croppingParameters: croppingParameters) { [weak self] _, image, _, _, _ in self?.imageView.image = image self?.dismiss(animated: true, completion: nil) } - + present(libraryViewController, animated: true, completion: nil) } - - @IBAction func libraryChanged(_ sender: Any) { + + @IBAction func libraryChanged(_: AnyObject) { libraryEnabled = !libraryEnabled } - + @IBAction func croppingChanged(_ sender: UISwitch) { croppingEnabled = sender.isOn croppingParametersView.isHidden = !sender.isOn @@ -71,4 +71,3 @@ class ViewController: UIViewController { minimumSizeLabel.text = "Minimum size: \(newValue.rounded())" } } - diff --git a/Podfile b/Podfile new file mode 100644 index 00000000..61127602 --- /dev/null +++ b/Podfile @@ -0,0 +1,17 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '10.0’ + +use_frameworks! + +target 'ALCameraViewController' do +pod 'ALCameraViewController', :git => 'https://github.com/cyclic/ALCameraViewController', :branch => 'develop' +end +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + config.build_settings['CODE_SIGNING_REQUIRED'] = 'NO' + config.build_settings['SWIFT_VERSION'] = '3.2' + end + end +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 00000000..fbef6bf1 --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - ALCameraViewController (3.0.4) + +DEPENDENCIES: + - ALCameraViewController (from `https://github.com/cyclic/ALCameraViewController`, branch `develop`) + +EXTERNAL SOURCES: + ALCameraViewController: + :branch: develop + :git: https://github.com/cyclic/ALCameraViewController + +CHECKOUT OPTIONS: + ALCameraViewController: + :commit: ce8df44bba96f506869022bbf86c4723b3e5d0f3 + :git: https://github.com/cyclic/ALCameraViewController + +SPEC CHECKSUMS: + ALCameraViewController: e29ad2b4967dd2550235f5222fffe83138956fdb + +PODFILE CHECKSUM: a59bd38ffaa1def3b32b28ba453b82b6514b2947 + +COCOAPODS: 1.3.1