diff --git a/.jazzy.yml b/.jazzy.yml index 3a318d2..762b120 100644 --- a/.jazzy.yml +++ b/.jazzy.yml @@ -5,6 +5,6 @@ github_url: https://github.com/carousell/pickle github_file_prefix: https://github.com/carousell/pickle/blob/master xcodebuild_arguments: [-project, Pickle.xcodeproj, -scheme, Pickle] module: Pickle -module_version: 1.3.1 +module_version: 1.4.0 output: docs/output theme: fullwidth diff --git a/CHANGELOG.md b/CHANGELOG.md index f281741..4cc7d96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Swift 4.2 * Move the framework and example targets into a single project +* Add new configurable to show live camera view cell in photo picker ## v1.3.1 diff --git a/Example/CarousellImagePickerController.swift b/Example/CarousellImagePickerController.swift index 8723870..6a963b1 100644 --- a/Example/CarousellImagePickerController.swift +++ b/Example/CarousellImagePickerController.swift @@ -76,6 +76,8 @@ private struct CarousellTheme: ImagePickerConfigurable { let doneBarButtonItem: UIBarButtonItem? = UIBarButtonItem(title: "Next", style: .plain, target: nil, action: nil) + let isLiveCameraViewEnabled: Bool? = true + // MARK: - Navigation Bar let navigationBarStyle: UIBarStyle? = .blackTranslucent diff --git a/Example/Images.xcassets/AppIcon.appiconset/Contents.json b/Example/Images.xcassets/AppIcon.appiconset/Contents.json index d3942e9..19882d5 100644 --- a/Example/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/Example/Images.xcassets/AppIcon.appiconset/Contents.json @@ -1,5 +1,15 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "29x29", @@ -29,10 +39,15 @@ "idiom" : "iphone", "size" : "60x60", "scale" : "3x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } -} +} \ No newline at end of file diff --git a/Pickle.podspec b/Pickle.podspec index c5c7b0f..81abd82 100644 --- a/Pickle.podspec +++ b/Pickle.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Pickle' - s.version = '1.3.1' + s.version = '1.4.0' s.summary = 'Carousell flavoured image picker with multiple photo selections.' s.homepage = 'https://github.com/carousell/pickle' s.license = { :type => 'Apache License 2.0', :file => 'LICENSE' } diff --git a/Pickle.xcodeproj/project.pbxproj b/Pickle.xcodeproj/project.pbxproj index 2343dcd..7abe7f3 100644 --- a/Pickle.xcodeproj/project.pbxproj +++ b/Pickle.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 20DB284F22560DB000037FEF /* LiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20DB284D22560DB000037FEF /* LiveView.swift */; }; + 20DB285022560DB000037FEF /* PhotoGalleryLiveViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20DB284E22560DB000037FEF /* PhotoGalleryLiveViewCell.swift */; }; + 20FC48032256FAC3004CCD1D /* CameraSessionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20FC48022256FAC3004CCD1D /* CameraSessionHandler.swift */; }; 5D5D6367F14D7B8C139E10E9 /* Pods_PickleUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D176E52E185B871CF8D97C4 /* Pods_PickleUITests.framework */; }; 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; @@ -78,6 +81,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 20DB284D22560DB000037FEF /* LiveView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveView.swift; sourceTree = ""; }; + 20DB284E22560DB000037FEF /* PhotoGalleryLiveViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoGalleryLiveViewCell.swift; sourceTree = ""; }; + 20FC48022256FAC3004CCD1D /* CameraSessionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraSessionHandler.swift; sourceTree = ""; }; 4B31C2D2E0E432A6637C5883 /* Pods_PickleExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PickleExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACD01AFB9204008FA782 /* PickleExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PickleExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -269,6 +275,9 @@ B57BEA54216AAFD700A2C776 /* PhotoGalleryCameraCell.swift */, B57BEA5E216AAFD700A2C776 /* PhotoGalleryCameraIconView.swift */, B57BEA57216AAFD700A2C776 /* PhotoGalleryCell.swift */, + 20DB284D22560DB000037FEF /* LiveView.swift */, + 20FC48022256FAC3004CCD1D /* CameraSessionHandler.swift */, + 20DB284E22560DB000037FEF /* PhotoGalleryLiveViewCell.swift */, B57BEA4F216AAFD700A2C776 /* PhotoGalleryHintLabel.swift */, B57BEA5B216AAFD700A2C776 /* PhotoGalleryTagLabel.swift */, B57BEA5F216AAFD700A2C776 /* PhotoGalleryViewController+UIViewControllerPreviewing.swift */, @@ -363,12 +372,13 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0830; - LastUpgradeCheck = 1000; + LastUpgradeCheck = 1010; ORGANIZATIONNAME = Carousell; TargetAttributes = { 607FACCF1AFB9204008FA782 = { CreatedOnToolsVersion = 6.3.1; LastSwiftMigration = 0900; + ProvisioningStyle = Manual; }; B563515A1F06278E00B5A46D = { CreatedOnToolsVersion = 8.3.3; @@ -516,6 +526,7 @@ buildActionMask = 2147483647; files = ( B57BEA78216AAFD700A2C776 /* CameraCompatible.swift in Sources */, + 20DB284F22560DB000037FEF /* LiveView.swift in Sources */, B57BEA74216AAFD700A2C776 /* ImagePickerConfigurable.swift in Sources */, B57BEA68216AAFD700A2C776 /* ImagePickerController.swift in Sources */, B57BEA72216AAFD700A2C776 /* ImagePickerControllerDelegate.swift in Sources */, @@ -529,7 +540,9 @@ B57BEA7A216AAFD700A2C776 /* PhotoGalleryCameraIconView.swift in Sources */, B57BEA73216AAFD700A2C776 /* PhotoGalleryCell.swift in Sources */, B57BEA6B216AAFD700A2C776 /* PhotoGalleryHintLabel.swift in Sources */, + 20FC48032256FAC3004CCD1D /* CameraSessionHandler.swift in Sources */, B57BEA77216AAFD700A2C776 /* PhotoGalleryTagLabel.swift in Sources */, + 20DB285022560DB000037FEF /* PhotoGalleryLiveViewCell.swift in Sources */, B57BEA7B216AAFD700A2C776 /* PhotoGalleryViewController+UIViewControllerPreviewing.swift in Sources */, B57BEA75216AAFD700A2C776 /* PhotoGalleryViewController.swift in Sources */, B57BEA6D216AAFD700A2C776 /* SlideDownDismissingAnimator.swift in Sources */, @@ -587,6 +600,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -643,6 +657,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -695,6 +710,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/Example/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; @@ -702,6 +718,7 @@ MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "com.carousell.pickle-example"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; }; name = Debug; @@ -713,6 +730,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/Example/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; @@ -720,6 +738,7 @@ MODULE_NAME = ExampleApp; PRODUCT_BUNDLE_IDENTIFIER = "com.carousell.pickle-example"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_SWIFT3_OBJC_INFERENCE = Default; }; name = Release; @@ -771,7 +790,7 @@ CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = ""; + CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; diff --git a/Pickle.xcodeproj/xcshareddata/xcschemes/Pickle-Example.xcscheme b/Pickle.xcodeproj/xcshareddata/xcschemes/Pickle-Example.xcscheme index 7677156..287380e 100644 --- a/Pickle.xcodeproj/xcshareddata/xcschemes/Pickle-Example.xcscheme +++ b/Pickle.xcodeproj/xcshareddata/xcschemes/Pickle-Example.xcscheme @@ -1,6 +1,6 @@ Bool + /// Tells the delegate that picker has finished launching camera with an array of selected assets + @objc optional func imagePickerController(_ picker: ImagePickerController, didFinishLaunchingCameraWith assets: [PHAsset]) + /// Tells the delegate that the user picked image assets. func imagePickerController(_ picker: ImagePickerController, didFinishPickingImageAssets assets: [PHAsset]) diff --git a/Pickle/Classes/LiveView.swift b/Pickle/Classes/LiveView.swift new file mode 100644 index 0000000..e6476d9 --- /dev/null +++ b/Pickle/Classes/LiveView.swift @@ -0,0 +1,36 @@ +// +// This source file is part of the carousell/pickle open source project +// +// Copyright © 2019 Carousell and the project authors +// Licensed under Apache License v2.0 +// +// See https://github.com/carousell/pickle/blob/master/LICENSE for license information +// See https://github.com/carousell/pickle/graphs/contributors for the list of project authors +// + +import AVFoundation +import UIKit + +final class LiveView: UIView { + + override class var layerClass: AnyClass { + return AVCaptureVideoPreviewLayer.self + } + + private var videoPreviewLayer: AVCaptureVideoPreviewLayer { + guard let layer = self.layer as? AVCaptureVideoPreviewLayer else { + fatalError("Expected `AVCaptureVideoPreviewLayer` type for layer. Check LiveView.layerClass implementation.") + } + layer.videoGravity = .resizeAspectFill + return layer + } + + var session: AVCaptureSession? { + get { + return videoPreviewLayer.session + } + set { + videoPreviewLayer.session = newValue + } + } +} diff --git a/Pickle/Classes/Parameters.swift b/Pickle/Classes/Parameters.swift index 4ea1a1a..b8a7aba 100644 --- a/Pickle/Classes/Parameters.swift +++ b/Pickle/Classes/Parameters.swift @@ -13,6 +13,7 @@ import UIKit /// A struct with placeholders that `ImagePickerConfigurable` requires. public struct Parameters: ImagePickerConfigurable { + /// Returns a configuration instance with default parameters. public init() {} @@ -79,4 +80,9 @@ public struct Parameters: ImagePickerConfigurable { /// The margin for the text of the hint label. public var hintTextMargin: UIEdgeInsets? + // MARK: - + + /// Specifies weather photo gallery should show a live preview + public var isLiveCameraViewEnabled: Bool? + } diff --git a/Pickle/Classes/PhotoGalleryLiveViewCell.swift b/Pickle/Classes/PhotoGalleryLiveViewCell.swift new file mode 100644 index 0000000..572c72a --- /dev/null +++ b/Pickle/Classes/PhotoGalleryLiveViewCell.swift @@ -0,0 +1,62 @@ +// +// This source file is part of the carousell/pickle open source project +// +// Copyright © 2019 Carousell and the project authors +// Licensed under Apache License v2.0 +// +// See https://github.com/carousell/pickle/blob/master/LICENSE for license information +// See https://github.com/carousell/pickle/graphs/contributors for the list of project authors +// + +import Foundation +import AVFoundation +import UIKit + +final class PhotoGalleryLiveViewCell: UICollectionViewCell { + + let previewView = LiveView() + + private lazy var cameraIconView: UIImageView = { + let camera = UIImage(named: "camera-icon", in: Bundle(for: type(of: self)), compatibleWith: nil) + return UIImageView(image: camera) + }() + + private lazy var textLabel: UILabel = { + let label = UILabel() + label.text = Bundle(for: type(of: self)).localizedString(forKey: "imagePicker.button.camera", value: "", table: nil).uppercased() + label.font = UIFont.forCameraButton + label.textColor = .white + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + setupViews() + } + + private func setupViews() { + isAccessibilityElement = true + + contentView.addSubview(previewView) + previewView.translatesAutoresizingMaskIntoConstraints = false + previewView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true + previewView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true + previewView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true + previewView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true + + contentView.addSubview(cameraIconView) + contentView.addSubview(textLabel) + + cameraIconView.translatesAutoresizingMaskIntoConstraints = false + cameraIconView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true + cameraIconView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: -10).isActive = true + + textLabel.translatesAutoresizingMaskIntoConstraints = false + textLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true + textLabel.topAnchor.constraint(equalTo: cameraIconView.bottomAnchor, constant: 10).isActive = true + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Pickle/Classes/PhotoGalleryViewController.swift b/Pickle/Classes/PhotoGalleryViewController.swift index 8de0d99..09ee7fa 100644 --- a/Pickle/Classes/PhotoGalleryViewController.swift +++ b/Pickle/Classes/PhotoGalleryViewController.swift @@ -73,6 +73,7 @@ internal final class PhotoGalleryViewController: UIViewController, return configuration?.preferredStatusBarUpdateAnimation ?? super.preferredStatusBarUpdateAnimation } + private var sessionHandler: CameraSessionHandler? private let album: PHAssetCollection private let configuration: ImagePickerConfigurable? internal private(set) lazy var isCameraCompatible: Bool = self.album.isCameraCompatible @@ -108,6 +109,7 @@ internal final class PhotoGalleryViewController: UIViewController, collectionView.backgroundColor = UIColor.white collectionView.register(PhotoGalleryCameraCell.self, forCellWithReuseIdentifier: String(describing: PhotoGalleryCameraCell.self)) collectionView.register(PhotoGalleryCell.self, forCellWithReuseIdentifier: String(describing: PhotoGalleryCell.self)) + collectionView.register(PhotoGalleryLiveViewCell.self, forCellWithReuseIdentifier: String(describing: PhotoGalleryLiveViewCell.self)) collectionView.allowsMultipleSelection = true return collectionView }() @@ -118,10 +120,22 @@ internal final class PhotoGalleryViewController: UIViewController, super.viewDidLoad() setUpSubviews() showEmptyViewIfNeeded() - if traitCollection.forceTouchCapability == .available { registerForPreviewing(with: self, sourceView: collectionView) } + if configuration?.isLiveCameraViewEnabled == .some(true) { + sessionHandler = try? CameraSessionHandler() + } + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + sessionHandler?.stopSession() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + try? sessionHandler?.startSession() } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -137,8 +151,17 @@ internal final class PhotoGalleryViewController: UIViewController, internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { if isCameraCompatible && indexPath.row == 0 { - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PhotoGalleryCameraCell.self), for: indexPath) - return cell + if sessionHandler?.hasPermssion == .some(true) { + let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: String(describing: PhotoGalleryLiveViewCell.self), + for: indexPath + ) + sessionHandler?.previewView = (cell as? PhotoGalleryLiveViewCell)?.previewView + return cell + } else { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PhotoGalleryCameraCell.self), for: indexPath) + return cell + } } let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: PhotoGalleryCell.self), for: indexPath) @@ -270,7 +293,6 @@ internal final class PhotoGalleryViewController: UIViewController, delegate?.photoGalleryViewControllerDidSelectCameraButton(self) } } - } diff --git a/README.md b/README.md index f167ba3..ff41f4e 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,9 @@ let picker = ImagePickerController(selectedAssets: assets) /// Asks the delegate if the image picker should launch camera with certain permission status. func imagePickerController(_ picker: ImagePickerController, shouldLaunchCameraWithAuthorization status: AVAuthorizationStatus) -> Bool + /// Tells the delegate that picker has finished launching camera with an array of selected assets + @objc optional func imagePickerController(_ picker: ImagePickerController, didFinishLaunchingCameraWith assets: [PHAsset]) + /// Tells the delegate that the user picked image assets. The delegate is responsible for dismissing the image picker. func imagePickerController(_ picker: ImagePickerController, didFinishPickingImageAssets assets: [PHAsset]) @@ -88,6 +91,11 @@ let picker = ImagePickerController( ) ``` +This functions will update `selectedAssets` property with new values and update the UI. It won't trigger any `delegate` callback +``` +public func updateSelectedAssets(with assets: [PHAsset]) { } +``` + ## Documentation ### Pickle Reference