From fcdc803cc829d2a0354f9891d7792166868a3aaf Mon Sep 17 00:00:00 2001 From: Mithilesh Parmar Date: Tue, 1 Jun 2021 22:44:19 +0530 Subject: [PATCH 1/9] add permisison handling to assetview controller, similar to whatsapp --- BSImagePicker.xcodeproj/project.pbxproj | 20 +++ Example.xcodeproj/project.pbxproj | 8 +- Example/ViewController.swift | 44 +++++- .../Controller/ImagePickerController.swift | 3 + .../ImagePickerControllerDelegate.swift | 2 + Sources/Extension/Alert+BSImagePicker.swift | 83 ++++++++++++ .../Extension/AutoLayout+BSImagePicker.swift | 53 ++++++++ .../PHAuthorization+BSImagePicker.swift | 49 +++++++ Sources/Extension/Size+BSImagePicker.swift | 34 +++++ Sources/Model/Settings.swift | 16 +++ .../AssetsCollectionViewDataSource.swift | 41 +++++- .../Scene/Assets/AssetsViewController.swift | 125 ++++++++++++------ .../Assets/AutorizationStatusHeaderView.swift | 122 +++++++++++++++++ 13 files changed, 550 insertions(+), 50 deletions(-) create mode 100644 Sources/Extension/Alert+BSImagePicker.swift create mode 100644 Sources/Extension/AutoLayout+BSImagePicker.swift create mode 100644 Sources/Extension/PHAuthorization+BSImagePicker.swift create mode 100644 Sources/Extension/Size+BSImagePicker.swift create mode 100644 Sources/Scene/Assets/AutorizationStatusHeaderView.swift diff --git a/BSImagePicker.xcodeproj/project.pbxproj b/BSImagePicker.xcodeproj/project.pbxproj index f0c03fc5..13943d60 100644 --- a/BSImagePicker.xcodeproj/project.pbxproj +++ b/BSImagePicker.xcodeproj/project.pbxproj @@ -7,6 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 2F0FADE226668A74005747B4 /* AutoLayout+BSImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0FADE126668A74005747B4 /* AutoLayout+BSImagePicker.swift */; }; + 2F0FADE526669B0F005747B4 /* Size+BSImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0FADE426669B0F005747B4 /* Size+BSImagePicker.swift */; }; + 2F0FADE826669B3C005747B4 /* AutorizationStatusHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0FADE726669B3C005747B4 /* AutorizationStatusHeaderView.swift */; }; + 2F0FADEC2666A140005747B4 /* Alert+BSImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0FADEB2666A140005747B4 /* Alert+BSImagePicker.swift */; }; + 2F0FADEF2666A185005747B4 /* PHAuthorization+BSImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0FADEE2666A185005747B4 /* PHAuthorization+BSImagePicker.swift */; }; 531D8C532249C81A00281681 /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531D8C522249C81A00281681 /* SettingsTests.swift */; }; 53FEDA162247FEB80098E34A /* CGSize+Scale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FEDA152247FEB80098E34A /* CGSize+Scale.swift */; }; 53FEDA18224805BA0098E34A /* CGSizeExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FEDA17224805BA0098E34A /* CGSizeExtensionTests.swift */; }; @@ -78,6 +83,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 2F0FADE126668A74005747B4 /* AutoLayout+BSImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AutoLayout+BSImagePicker.swift"; sourceTree = ""; }; + 2F0FADE426669B0F005747B4 /* Size+BSImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Size+BSImagePicker.swift"; sourceTree = ""; }; + 2F0FADE726669B3C005747B4 /* AutorizationStatusHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutorizationStatusHeaderView.swift; sourceTree = ""; }; + 2F0FADEB2666A140005747B4 /* Alert+BSImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Alert+BSImagePicker.swift"; sourceTree = ""; }; + 2F0FADEE2666A185005747B4 /* PHAuthorization+BSImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PHAuthorization+BSImagePicker.swift"; sourceTree = ""; }; 531D8C522249C81A00281681 /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = ""; }; 53FEDA152247FEB80098E34A /* CGSize+Scale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGSize+Scale.swift"; sourceTree = ""; }; 53FEDA17224805BA0098E34A /* CGSizeExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGSizeExtensionTests.swift; sourceTree = ""; }; @@ -222,6 +232,7 @@ C2DC13C923F75BDA0035FD13 /* NumberView.swift */, 55BCF8C521D52C1000386752 /* CameraCollectionViewCell.swift */, 55F67B7A222F088500805134 /* GradientView.swift */, + 2F0FADE726669B3C005747B4 /* AutorizationStatusHeaderView.swift */, ); path = Assets; sourceTree = ""; @@ -337,6 +348,10 @@ isa = PBXGroup; children = ( 94DA686F247BDE5900CD5251 /* UIColor+BSImagePicker.swift */, + 2F0FADE126668A74005747B4 /* AutoLayout+BSImagePicker.swift */, + 2F0FADE426669B0F005747B4 /* Size+BSImagePicker.swift */, + 2F0FADEB2666A140005747B4 /* Alert+BSImagePicker.swift */, + 2F0FADEE2666A185005747B4 /* PHAuthorization+BSImagePicker.swift */, ); path = Extension; sourceTree = ""; @@ -462,6 +477,8 @@ 550CB425230FD01200543217 /* ImageView.swift in Sources */, 55BCF8D521D52C1000386752 /* AssetCollectionViewCell.swift in Sources */, 559DB81B21E6B43400CD58B4 /* ImagePickerController+ButtonActions.swift in Sources */, + 2F0FADEC2666A140005747B4 /* Alert+BSImagePicker.swift in Sources */, + 2F0FADE226668A74005747B4 /* AutoLayout+BSImagePicker.swift in Sources */, 559DB81121E6561300CD58B4 /* ImagePickerController+Closure.swift in Sources */, 55C5B802222BD529003CF3F1 /* DropdownPresentationController.swift in Sources */, 55BCF8E021D52C1000386752 /* ZoomAnimator.swift in Sources */, @@ -471,11 +488,13 @@ 94DA6870247BDE5900CD5251 /* UIColor+BSImagePicker.swift in Sources */, 5554729D21E5248400B90CA5 /* ImagePickerController.swift in Sources */, 55C5B800222BD445003CF3F1 /* DropdownTransitionDelegate.swift in Sources */, + 2F0FADE526669B0F005747B4 /* Size+BSImagePicker.swift in Sources */, 55BCF8D321D52C1000386752 /* AssetsCollectionViewDataSource.swift in Sources */, 550CB427230FD08200543217 /* ImageViewLayout.swift in Sources */, 55BCF8D621D52C1000386752 /* AlbumCell.swift in Sources */, 55BCF8D021D52C1000386752 /* AlbumsTableViewDataSource.swift in Sources */, 55F67B77222EEB2500805134 /* VideoCollectionViewCell.swift in Sources */, + 2F0FADE826669B3C005747B4 /* AutorizationStatusHeaderView.swift in Sources */, 559DB80F21E655D000CD58B4 /* ImagePickerControllerDelegate.swift in Sources */, 559DB81721E6AFD800CD58B4 /* ImagePickerController+Assets.swift in Sources */, 55CDB45B223435420050D572 /* PlayerView.swift in Sources */, @@ -485,6 +504,7 @@ 55BCF8D721D52C1000386752 /* CheckmarkView.swift in Sources */, 555472AE21E538B000B90CA5 /* AssetsViewController.swift in Sources */, 5543942D232A4EB500DB51B7 /* LivePreviewViewController.swift in Sources */, + 2F0FADEF2666A185005747B4 /* PHAuthorization+BSImagePicker.swift in Sources */, C2DC13CA23F75BDB0035FD13 /* NumberView.swift in Sources */, 55BCF8DC21D52C1000386752 /* PreviewViewController.swift in Sources */, 55BCF8CF21D52C1000386752 /* Settings.swift in Sources */, diff --git a/Example.xcodeproj/project.pbxproj b/Example.xcodeproj/project.pbxproj index 9538c31c..506bfc3c 100644 --- a/Example.xcodeproj/project.pbxproj +++ b/Example.xcodeproj/project.pbxproj @@ -432,14 +432,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = Y2NHS7RD78; + DEVELOPMENT_TEAM = F85Q68998X; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Example/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.Example; + PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.Example.mithilesh; PRODUCT_NAME = ImagePicker; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -451,14 +451,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = Y2NHS7RD78; + DEVELOPMENT_TEAM = F85Q68998X; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Example/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.Example; + PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.Example.mithilesh; PRODUCT_NAME = ImagePicker; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/Example/ViewController.swift b/Example/ViewController.swift index a36de3fb..052e6340 100644 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -76,7 +76,7 @@ class ViewController: UIViewController { return 2 } } - + self.presentImagePicker(imagePicker, select: { (asset) in print("Selected: \(asset)") }, deselect: { (asset) in @@ -91,16 +91,17 @@ class ViewController: UIViewController { @IBAction func showImagePickerWithSelectedAssets(_ sender: UIButton) { let allAssets = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: nil) var evenAssets = [PHAsset]() - + allAssets.enumerateObjects({ (asset, idx, stop) -> Void in if idx % 2 == 0 { evenAssets.append(asset) } }) - + let imagePicker = ImagePickerController(selectedAssets: evenAssets) imagePicker.settings.fetch.assets.supportedMediaTypes = [.image] - + + self.presentImagePicker(imagePicker, select: { (asset) in print("Selected: \(asset)") }, deselect: { (asset) in @@ -111,5 +112,40 @@ class ViewController: UIViewController { print("Finished with selections: \(assets)") }) } + + func showImagePickerWithPermissionHandling() { + let imagePicker = ImagePickerController() + imagePicker.settings.selection.max = 5 + imagePicker.settings.theme.selectionStyle = .numbered + imagePicker.settings.fetch.assets.supportedMediaTypes = [.image, .video] + imagePicker.settings.selection.unselectOnReachingMax = true + imagePicker.settings.permission.enabled = true + + imagePicker.imagePickerDelegate = self + self.present(imagePicker, animated: true, completion: nil) + } + } + +extension ViewController: ImagePickerControllerDelegate { + func imagePicker(_ imagePicker: ImagePickerController, didSelectAsset asset: PHAsset) { + print("Selected: \(asset)") + } + + func imagePicker(_ imagePicker: ImagePickerController, didDeselectAsset asset: PHAsset) { + print("Deselected: \(asset)") + } + + func imagePicker(_ imagePicker: ImagePickerController, didFinishWithAssets assets: [PHAsset]) { + print("Finished with selections: \(assets)") + } + + func imagePicker(_ imagePicker: ImagePickerController, didCancelWithAssets assets: [PHAsset]) { + print("Canceled with selections: \(assets)") + } + + func imagePicker(_ imagePicker: ImagePickerController, didReachSelectionLimit count: Int) { + + } +} diff --git a/Sources/Controller/ImagePickerController.swift b/Sources/Controller/ImagePickerController.swift index b3a4af0f..9258fd0d 100644 --- a/Sources/Controller/ImagePickerController.swift +++ b/Sources/Controller/ImagePickerController.swift @@ -28,6 +28,7 @@ import Photos @objcMembers open class ImagePickerController: UINavigationController { // MARK: Public properties public weak var imagePickerDelegate: ImagePickerControllerDelegate? + public var settings: Settings = Settings() public var doneButton: UIBarButtonItem = UIBarButtonItem(title: "", style: .done, target: nil, action: nil) public var cancelButton: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: nil, action: nil) @@ -47,6 +48,7 @@ import Photos var onDeselection: ((_ asset: PHAsset) -> Void)? var onCancel: ((_ assets: [PHAsset]) -> Void)? var onFinish: ((_ assets: [PHAsset]) -> Void)? + var onPermissionChange: ((_ status: PHAuthorizationStatus) -> Void)? let assetsViewController: AssetsViewController let albumsViewController = AlbumsViewController() @@ -93,6 +95,7 @@ import Photos // Setup view controllers albumsViewController.delegate = self assetsViewController.delegate = self + viewControllers = [assetsViewController] view.backgroundColor = settings.theme.backgroundColor diff --git a/Sources/Controller/ImagePickerControllerDelegate.swift b/Sources/Controller/ImagePickerControllerDelegate.swift index 51bd48c7..b3a26f13 100644 --- a/Sources/Controller/ImagePickerControllerDelegate.swift +++ b/Sources/Controller/ImagePickerControllerDelegate.swift @@ -49,4 +49,6 @@ public protocol ImagePickerControllerDelegate: class { /// - Parameter imagePicker: The image picker that selection limit was reached in. /// - Parameter count: Number of selected assets. func imagePicker(_ imagePicker: ImagePickerController, didReachSelectionLimit count: Int) + } + diff --git a/Sources/Extension/Alert+BSImagePicker.swift b/Sources/Extension/Alert+BSImagePicker.swift new file mode 100644 index 00000000..88718a6c --- /dev/null +++ b/Sources/Extension/Alert+BSImagePicker.swift @@ -0,0 +1,83 @@ +// The MIT License (MIT) +// +// Copyright (c) 2021 Mithilesh Parmar +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import UIKit +import Photos + +extension AssetsViewController { + internal func handleDeniedAccess(){ + let alert = UIAlertController(title: "Allow access to your photos", + message: "This lets you share from your camera roll and enables other features for photos. Go to your settings and tap \"Photos\".", + preferredStyle: .alert) + + let notNowAction = UIAlertAction(title: "Not Now", + style: .cancel, + handler: nil) + alert.addAction(notNowAction) + + let openSettingsAction = UIAlertAction(title: "Open Settings", + style: .default) { [unowned self] (_) in + // Open app privacy settings + gotoAppPrivacySettings() + } + alert.addAction(openSettingsAction) + present(alert, animated: true, completion: nil) + } + + internal func handleLimitedAccess(){ + let actionSheet = UIAlertController(title: "", + message: "Select more photos or go to Settings to allow access to all photos.", + preferredStyle: .actionSheet) + + let selectPhotosAction = UIAlertAction(title: "Select more photos", + style: .default) { [unowned self] (_) in + // Show limited library picker + if #available(iOS 14, *) { + PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: self) + } + } + actionSheet.addAction(selectPhotosAction) + + let allowFullAccessAction = UIAlertAction(title: "Allow access to all photos", + style: .default) { [unowned self] (_) in + // Open app privacy settings + self.gotoAppPrivacySettings() + } + actionSheet.addAction(allowFullAccessAction) + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + actionSheet.addAction(cancelAction) + + present(actionSheet, animated: true, completion: nil) + + } + + internal func gotoAppPrivacySettings() { + guard let url = URL(string: UIApplication.openSettingsURLString), + UIApplication.shared.canOpenURL(url) else { + assertionFailure("Not able to open App privacy settings") + return + } + + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } +} diff --git a/Sources/Extension/AutoLayout+BSImagePicker.swift b/Sources/Extension/AutoLayout+BSImagePicker.swift new file mode 100644 index 00000000..4d240d04 --- /dev/null +++ b/Sources/Extension/AutoLayout+BSImagePicker.swift @@ -0,0 +1,53 @@ +// The MIT License (MIT) +// +// Copyright (c) 2021 Mithilesh Parmar +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Foundation + + +@propertyWrapper +public struct UsesAutoLayout { + public var wrappedValue: T { + didSet { + wrappedValue.translatesAutoresizingMaskIntoConstraints = false + } + } + + public init(wrappedValue: T) { + self.wrappedValue = wrappedValue + wrappedValue.translatesAutoresizingMaskIntoConstraints = false + } +} + + +@propertyWrapper +public struct HasUserInteraction { + public var wrappedValue: T { + didSet { + wrappedValue.isUserInteractionEnabled = true + } + } + + public init(wrappedValue: T) { + self.wrappedValue = wrappedValue + wrappedValue.isUserInteractionEnabled = true + } +} diff --git a/Sources/Extension/PHAuthorization+BSImagePicker.swift b/Sources/Extension/PHAuthorization+BSImagePicker.swift new file mode 100644 index 00000000..29c96718 --- /dev/null +++ b/Sources/Extension/PHAuthorization+BSImagePicker.swift @@ -0,0 +1,49 @@ +// The MIT License (MIT) +// +// Copyright (c) 2021 Mithilesh Parmar +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import UIKit +import Photos + + +extension AssetsViewController { + + internal func checkAuthorizationStatus(){ + + if #available(iOS 14, *) { + PHPhotoLibrary.requestAuthorization(for: .readWrite) { [unowned self] (status) in + DispatchQueue.main.async { + self.dataSource.setStatus(status) + self.collectionView.reloadData() + } + } + }else{ + PHPhotoLibrary.requestAuthorization { (status) in + DispatchQueue.main.async { + self.dataSource.setStatus(status) + self.collectionView.reloadData() + } + } + + } + } + +} diff --git a/Sources/Extension/Size+BSImagePicker.swift b/Sources/Extension/Size+BSImagePicker.swift new file mode 100644 index 00000000..16883e38 --- /dev/null +++ b/Sources/Extension/Size+BSImagePicker.swift @@ -0,0 +1,34 @@ +// The MIT License (MIT) +// +// Copyright (c) 2021 Mithilesh Parmar +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import UIKit + +extension UIView { + func runtimeSize()-> CGSize { + let size: CGSize = self.systemLayoutSizeFitting( + CGSize(width: UIScreen.main.bounds.width, + height: UIView.layoutFittingExpandedSize.height), + withHorizontalFittingPriority: .fittingSizeLevel, + verticalFittingPriority: .fittingSizeLevel) + return size + } +} diff --git a/Sources/Model/Settings.swift b/Sources/Model/Settings.swift index da951b22..7773806e 100755 --- a/Sources/Model/Settings.swift +++ b/Sources/Model/Settings.swift @@ -199,6 +199,19 @@ import Photos /// Allow the user to dismiss the image picker by swiping down public lazy var allowSwipe = false } + + public class Permission: NSObject { + /// should the image picker check for permission when loaded and give callbacks + public lazy var enabled = false + + public lazy var permissionDeniedWarningText: String = "Permission is required to access photos library." + public lazy var limitedPermissionWarningText: String = "You've given access to only select number of photos." + + public lazy var permissionDeniedHeaderBackgroundColor: UIColor = .clear + public lazy var limitedPermissionHeaderBackgroundColor: UIColor = .clear + + public lazy var manageButtonText: String = "Manage" + } /// Theme settings public lazy var theme = Theme() @@ -217,4 +230,7 @@ import Photos /// Preview options public lazy var preview = Preview() + + /// permission options + public lazy var permission = Permission() } diff --git a/Sources/Scene/Assets/AssetsCollectionViewDataSource.swift b/Sources/Scene/Assets/AssetsCollectionViewDataSource.swift index 509128f9..50220c9b 100755 --- a/Sources/Scene/Assets/AssetsCollectionViewDataSource.swift +++ b/Sources/Scene/Assets/AssetsCollectionViewDataSource.swift @@ -23,11 +23,13 @@ import UIKit import Photos -class AssetsCollectionViewDataSource : NSObject, UICollectionViewDataSource { +class AssetsCollectionViewDataSource : NSObject, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { private static let assetCellIdentifier = "AssetCell" private static let videoCellIdentifier = "VideoCell" + private var authorizationStatus: PHAuthorizationStatus? var settings: Settings! + var fetchResult: PHFetchResult { didSet { imageManager.stopCachingImagesForAllAssets() @@ -38,11 +40,13 @@ class AssetsCollectionViewDataSource : NSObject, UICollectionViewDataSource { imageManager.stopCachingImagesForAllAssets() } } - + private let imageManager = PHCachingImageManager() private let durationFormatter = DateComponentsFormatter() private let store: AssetStore private let contentMode: PHImageContentMode = .aspectFill + weak var headerViewDelegate: AutorizationStatusHeaderViewDelegate? + init(fetchResult: PHFetchResult, store: AssetStore) { self.fetchResult = fetchResult @@ -61,6 +65,8 @@ class AssetsCollectionViewDataSource : NSObject, UICollectionViewDataSource { return fetchResult.count } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let asset = fetchResult[indexPath.row] let animationsWasEnabled = UIView.areAnimationsEnabled @@ -75,7 +81,7 @@ class AssetsCollectionViewDataSource : NSObject, UICollectionViewDataSource { cell = collectionView.dequeueReusableCell(withReuseIdentifier: AssetsCollectionViewDataSource.assetCellIdentifier, for: indexPath) as! AssetCollectionViewCell } UIView.setAnimationsEnabled(animationsWasEnabled) - + cell.accessibilityIdentifier = "Photo \(indexPath.item + 1)" cell.accessibilityTraits = UIAccessibilityTraits.button cell.isAccessibilityElement = true @@ -91,6 +97,7 @@ class AssetsCollectionViewDataSource : NSObject, UICollectionViewDataSource { static func registerCellIdentifiersForCollectionView(_ collectionView: UICollectionView?) { collectionView?.register(AssetCollectionViewCell.self, forCellWithReuseIdentifier: assetCellIdentifier) collectionView?.register(VideoCollectionViewCell.self, forCellWithReuseIdentifier: videoCellIdentifier) + collectionView?.register(AutorizationStatusHeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: AutorizationStatusHeaderView.id) } private func loadImage(for asset: PHAsset, in cell: AssetCollectionViewCell) { @@ -105,6 +112,32 @@ class AssetsCollectionViewDataSource : NSObject, UICollectionViewDataSource { cell.imageView.image = image }) } + + func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + + if kind == UICollectionView.elementKindSectionHeader { + + let cell = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: AutorizationStatusHeaderView.id, for: indexPath) as! AutorizationStatusHeaderView + cell.delegate = headerViewDelegate + cell.buttonText = settings.permission.manageButtonText + cell.limitedPermissionBackgroundColor = settings.permission.limitedPermissionHeaderBackgroundColor + cell.limitedPermissionGrantedText = settings.permission.limitedPermissionWarningText + cell.permissionDeniedText = settings.permission.permissionDeniedWarningText + cell.permissionDeniedBackgroundColor = settings.permission.permissionDeniedHeaderBackgroundColor + cell.authorizationStatus = authorizationStatus + return cell + } + + fatalError("No header of kind \(kind) registerd for section \(indexPath.section)") + } + + func setStatus(_ status: PHAuthorizationStatus) { + self.authorizationStatus = status + } + + func getStatus() -> PHAuthorizationStatus? { + return self.authorizationStatus + } } extension AssetsCollectionViewDataSource: UICollectionViewDataSourcePrefetching { @@ -112,7 +145,7 @@ extension AssetsCollectionViewDataSource: UICollectionViewDataSourcePrefetching let assets = indexPaths.map { fetchResult[$0.row] } imageManager.startCachingImages(for: assets, targetSize: imageSize, contentMode: contentMode, options: nil) } - + func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) { } } diff --git a/Sources/Scene/Assets/AssetsViewController.swift b/Sources/Scene/Assets/AssetsViewController.swift index 389730e2..7900d789 100644 --- a/Sources/Scene/Assets/AssetsViewController.swift +++ b/Sources/Scene/Assets/AssetsViewController.swift @@ -31,66 +31,76 @@ protocol AssetsViewControllerDelegate: class { class AssetsViewController: UIViewController { weak var delegate: AssetsViewControllerDelegate? + + var settings: Settings! { didSet { dataSource.settings = settings } } - + private let store: AssetStore - private let collectionView: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + internal let collectionView: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) private var fetchResult: PHFetchResult = PHFetchResult() { didSet { dataSource.fetchResult = fetchResult } } - private let dataSource: AssetsCollectionViewDataSource - + + internal let dataSource: AssetsCollectionViewDataSource + private let selectionFeedback = UISelectionFeedbackGenerator() - + + init(store: AssetStore) { self.store = store dataSource = AssetsCollectionViewDataSource(fetchResult: fetchResult, store: store) super.init(nibName: nil, bundle: nil) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + deinit { PHPhotoLibrary.shared().unregisterChangeObserver(self) } - + override func viewDidLoad() { super.viewDidLoad() - + PHPhotoLibrary.shared().register(self) - + view = collectionView - + // Set an empty title to get < back button title = " " - + collectionView.allowsMultipleSelection = true collectionView.bounces = true collectionView.alwaysBounceVertical = true collectionView.backgroundColor = settings.theme.backgroundColor collectionView.delegate = self + dataSource.headerViewDelegate = self collectionView.dataSource = dataSource collectionView.prefetchDataSource = dataSource AssetsCollectionViewDataSource.registerCellIdentifiersForCollectionView(collectionView) - + let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(AssetsViewController.collectionViewLongPressed(_:))) longPressRecognizer.minimumPressDuration = 0.5 collectionView.addGestureRecognizer(longPressRecognizer) - + + if settings.permission.enabled { + checkAuthorizationStatus() + } + + syncSelections(store.assets) } - + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) updateCollectionViewLayout(for: traitCollection) } - + func showAssets(in album: PHAssetCollection) { fetchResult = PHAsset.fetchAssets(in: album, options: settings.fetch.assets.options) collectionView.reloadData() @@ -98,10 +108,10 @@ class AssetsViewController: UIViewController { syncSelections(selections) collectionView.setContentOffset(.zero, animated: false) } - + private func syncSelections(_ assets: [PHAsset]) { collectionView.allowsMultipleSelection = true - + // Unselect all for indexPath in collectionView.indexPathsForSelectedItems ?? [] { collectionView.deselectItem(at: indexPath, animated: false) @@ -116,13 +126,13 @@ class AssetsViewController: UIViewController { updateSelectionIndexForCell(at: indexPath) } } - + func unselect(asset: PHAsset) { let index = fetchResult.index(of: asset) guard index != NSNotFound else { return } let indexPath = IndexPath(item: index, section: 0) collectionView.deselectItem(at:indexPath, animated: false) - + for indexPath in collectionView.indexPathsForSelectedItems ?? [] { updateSelectionIndexForCell(at: indexPath) } @@ -132,37 +142,37 @@ class AssetsViewController: UIViewController { super.traitCollectionDidChange(previousTraitCollection) updateCollectionViewLayout(for: traitCollection) } - + @objc func collectionViewLongPressed(_ sender: UILongPressGestureRecognizer) { guard settings.preview.enabled else { return } guard sender.state == .began else { return } - + selectionFeedback.selectionChanged() - + // Calculate which index path long press came from let location = sender.location(in: collectionView) guard let indexPath = collectionView.indexPathForItem(at: location) else { return } guard let cell = collectionView.cellForItem(at: indexPath) as? AssetCollectionViewCell else { return } let asset = fetchResult.object(at: indexPath.row) - + delegate?.assetsViewController(self, didLongPressCell: cell, displayingAsset: asset) } - + private func updateCollectionViewLayout(for traitCollection: UITraitCollection) { guard let collectionViewFlowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return } - + let itemSpacing = settings.list.spacing let itemsPerRow = settings.list.cellsPerRow(traitCollection.verticalSizeClass, traitCollection.horizontalSizeClass) let itemWidth = (collectionView.bounds.width - CGFloat(itemsPerRow - 1) * itemSpacing) / CGFloat(itemsPerRow) let itemSize = CGSize(width: itemWidth, height: itemWidth) - + collectionViewFlowLayout.minimumLineSpacing = itemSpacing collectionViewFlowLayout.minimumInteritemSpacing = itemSpacing collectionViewFlowLayout.itemSize = itemSize - + dataSource.imageSize = itemSize.resize(by: UIScreen.main.scale) } - + private func updateSelectionIndexForCell(at indexPath: IndexPath) { guard settings.theme.selectionStyle == .numbered else { return } guard let cell = collectionView.cellForItem(at: indexPath) as? AssetCollectionViewCell else { return } @@ -171,17 +181,39 @@ class AssetsViewController: UIViewController { } } -extension AssetsViewController: UICollectionViewDelegate { +extension AssetsViewController: UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { + + guard let status = dataSource.getStatus() else { + return CGSize(width: UIScreen.main.bounds.width, height: 0) + } + + + if status == .denied { + return CGSize(width: UIScreen.main.bounds.width, height: 80) + } + + if #available(iOS 14, *) { + if status == .limited { + return CGSize(width: UIScreen.main.bounds.width, height: 80) + } + } + + return CGSize(width: UIScreen.main.bounds.width, height: 0) + + } + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { selectionFeedback.selectionChanged() - + let asset = fetchResult.object(at: indexPath.row) store.append(asset) delegate?.assetsViewController(self, didSelectAsset: asset) - + updateSelectionIndexForCell(at: indexPath) } - + func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { selectionFeedback.selectionChanged() @@ -193,13 +225,14 @@ extension AssetsViewController: UICollectionViewDelegate { updateSelectionIndexForCell(at: indexPath) } } - + func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { guard store.count < settings.selection.max || settings.selection.unselectOnReachingMax else { return false } selectionFeedback.prepare() - + return true } + } extension AssetsViewController: PHPhotoLibraryChangeObserver { @@ -210,7 +243,7 @@ extension AssetsViewController: PHPhotoLibraryChangeObserver { if changes.hasIncrementalChanges { self.collectionView.performBatchUpdates({ self.fetchResult = changes.fetchResultAfterChanges - + // For indexes to make sense, updates must be in this order: // delete, insert, move if let removed = changes.removedIndexes, removed.count > 0 { @@ -231,7 +264,7 @@ extension AssetsViewController: PHPhotoLibraryChangeObserver { to: IndexPath(item: toIndex, section: 0)) } }) - + // "Use these indices to reconfigure the corresponding cells after performBatchUpdates" // https://developer.apple.com/documentation/photokit/phobjectchangedetails if let changed = changes.changedIndexes, changed.count > 0 { @@ -241,9 +274,25 @@ extension AssetsViewController: PHPhotoLibraryChangeObserver { self.fetchResult = changes.fetchResultAfterChanges self.collectionView.reloadData() } - + // No matter if we have incremental changes or not, sync the selections self.syncSelections(self.store.assets) } } } + +extension AssetsViewController: AutorizationStatusHeaderViewDelegate { + func didTapManageButton(for status: PHAuthorizationStatus) { + switch status { + case .denied: + self.handleDeniedAccess() + break + case .limited: + self.handleLimitedAccess() + break + default: + break + } + } +} + diff --git a/Sources/Scene/Assets/AutorizationStatusHeaderView.swift b/Sources/Scene/Assets/AutorizationStatusHeaderView.swift new file mode 100644 index 00000000..a5bcca39 --- /dev/null +++ b/Sources/Scene/Assets/AutorizationStatusHeaderView.swift @@ -0,0 +1,122 @@ +// The MIT License (MIT) +// +// Copyright (c) 2021 Mithilesh Parmar +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import UIKit +import Photos + +protocol AutorizationStatusHeaderViewDelegate: class { + func didTapManageButton(for status: PHAuthorizationStatus) +} + +class AutorizationStatusHeaderView : UICollectionReusableView { + static let id = "PHAutorizationHeaderView" + + weak var delegate: AutorizationStatusHeaderViewDelegate? + + @UsesAutoLayout + private var titleLabel: UILabel = { + let lbl = UILabel() + lbl.numberOfLines = 3 + return lbl + }() + + @UsesAutoLayout + private var manageButton: UIButton = { + let btn = UIButton() + return btn + }() + + var buttonText: String? { + didSet{ + manageButton.setTitle(buttonText, for: .normal) + } + } + + var permissionDeniedText: String? + var permissionDeniedBackgroundColor: UIColor? + + var limitedPermissionGrantedText: String? + var limitedPermissionBackgroundColor: UIColor? + + var authorizationStatus: PHAuthorizationStatus?{ + didSet { + guard let status = authorizationStatus else { return } + switch status { + case .denied: + titleLabel.text = permissionDeniedText + backgroundColor = permissionDeniedBackgroundColor + layoutIfNeeded() + break + case .limited: + titleLabel.text = limitedPermissionGrantedText + backgroundColor = limitedPermissionBackgroundColor + layoutIfNeeded() + break + default: + break + } + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + addViews() + constraintLayout() + addTargets() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func addViews(){ + addSubview(titleLabel) + addSubview(manageButton) + } + + private func constraintLayout(){ + NSLayoutConstraint.activate([ + titleLabel.topAnchor.constraint(equalTo: self.topAnchor), + titleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor), + titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16), + titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: self.manageButton.leadingAnchor, constant: -8), + + manageButton.topAnchor.constraint(equalTo: self.topAnchor), + manageButton.bottomAnchor.constraint(equalTo: self.bottomAnchor), + manageButton.leadingAnchor.constraint(greaterThanOrEqualTo: self.titleLabel.trailingAnchor, constant: 8), + manageButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16), + manageButton.widthAnchor.constraint(greaterThanOrEqualToConstant: manageButton.runtimeSize().width) + + + ]) + } + + private func addTargets(){ + manageButton.addTarget(self, action: #selector(didTapManage), for: .touchUpInside) + } + + @objc func didTapManage(){ + guard let status = authorizationStatus else { return } + delegate?.didTapManageButton(for: status) + } + +} From ecca308a5e0b3641c327a625d7867ba1aa2eaef6 Mon Sep 17 00:00:00 2001 From: Mithilesh Parmar Date: Tue, 1 Jun 2021 23:09:04 +0530 Subject: [PATCH 2/9] add textcolors for permission header view --- Sources/Model/Settings.swift | 8 ++++++-- .../Assets/AssetsCollectionViewDataSource.swift | 6 ++++++ .../Scene/Assets/AutorizationStatusHeaderView.swift | 12 ++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Sources/Model/Settings.swift b/Sources/Model/Settings.swift index 7773806e..f4cf4744 100755 --- a/Sources/Model/Settings.swift +++ b/Sources/Model/Settings.swift @@ -205,12 +205,16 @@ import Photos public lazy var enabled = false public lazy var permissionDeniedWarningText: String = "Permission is required to access photos library." - public lazy var limitedPermissionWarningText: String = "You've given access to only select number of photos." - public lazy var permissionDeniedHeaderBackgroundColor: UIColor = .clear + public lazy var permissionDeniedWarningTextColor: UIColor = .red + + public lazy var limitedPermissionWarningText: String = "You've given access to only select number of photos." public lazy var limitedPermissionHeaderBackgroundColor: UIColor = .clear + public lazy var limitedPermissionWarningTextColor: UIColor = .systemPrimaryTextColor + public lazy var manageButtonText: String = "Manage" + public lazy var manageButtonTextColor: UIColor = .systemBlue } /// Theme settings diff --git a/Sources/Scene/Assets/AssetsCollectionViewDataSource.swift b/Sources/Scene/Assets/AssetsCollectionViewDataSource.swift index 50220c9b..ef5a0d33 100755 --- a/Sources/Scene/Assets/AssetsCollectionViewDataSource.swift +++ b/Sources/Scene/Assets/AssetsCollectionViewDataSource.swift @@ -120,10 +120,16 @@ class AssetsCollectionViewDataSource : NSObject, UICollectionViewDataSource, UIC let cell = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: AutorizationStatusHeaderView.id, for: indexPath) as! AutorizationStatusHeaderView cell.delegate = headerViewDelegate cell.buttonText = settings.permission.manageButtonText + cell.buttonTextColor = settings.permission.manageButtonTextColor + cell.limitedPermissionBackgroundColor = settings.permission.limitedPermissionHeaderBackgroundColor cell.limitedPermissionGrantedText = settings.permission.limitedPermissionWarningText + cell.limitedPermissionTextColor = settings.permission.limitedPermissionWarningTextColor + cell.permissionDeniedText = settings.permission.permissionDeniedWarningText cell.permissionDeniedBackgroundColor = settings.permission.permissionDeniedHeaderBackgroundColor + cell.permissionDeniedTextColor = settings.permission.permissionDeniedWarningTextColor + cell.authorizationStatus = authorizationStatus return cell } diff --git a/Sources/Scene/Assets/AutorizationStatusHeaderView.swift b/Sources/Scene/Assets/AutorizationStatusHeaderView.swift index a5bcca39..dd11f6b1 100644 --- a/Sources/Scene/Assets/AutorizationStatusHeaderView.swift +++ b/Sources/Scene/Assets/AutorizationStatusHeaderView.swift @@ -51,11 +51,21 @@ class AutorizationStatusHeaderView : UICollectionReusableView { } } + var buttonTextColor: UIColor? { + didSet{ + manageButton.setTitleColor(buttonTextColor, for: .normal) + } + } + + + var permissionDeniedText: String? var permissionDeniedBackgroundColor: UIColor? + var permissionDeniedTextColor: UIColor? var limitedPermissionGrantedText: String? var limitedPermissionBackgroundColor: UIColor? + var limitedPermissionTextColor: UIColor? var authorizationStatus: PHAuthorizationStatus?{ didSet { @@ -63,11 +73,13 @@ class AutorizationStatusHeaderView : UICollectionReusableView { switch status { case .denied: titleLabel.text = permissionDeniedText + titleLabel.textColor = permissionDeniedTextColor backgroundColor = permissionDeniedBackgroundColor layoutIfNeeded() break case .limited: titleLabel.text = limitedPermissionGrantedText + titleLabel.textColor = limitedPermissionTextColor backgroundColor = limitedPermissionBackgroundColor layoutIfNeeded() break From fa56af0332ba01ec6d913a72ba77b90b66cef531 Mon Sep 17 00:00:00 2001 From: Mithilesh Parmar Date: Sun, 19 Dec 2021 17:29:23 +0530 Subject: [PATCH 3/9] resolve: all comments mentioned on PR from @bivant --- BSImagePicker.xcodeproj/project.pbxproj | 8 --- Example/ViewController.swift | 1 + .../Controller/ImagePickerController.swift | 1 - Sources/Extension/Alert+BSImagePicker.swift | 4 +- .../Extension/AutoLayout+BSImagePicker.swift | 53 ------------------- .../PHAuthorization+BSImagePicker.swift | 49 ----------------- .../Scene/Assets/AssetsViewController.swift | 22 +++++--- .../Assets/AutorizationStatusHeaderView.swift | 18 +++---- 8 files changed, 24 insertions(+), 132 deletions(-) delete mode 100644 Sources/Extension/AutoLayout+BSImagePicker.swift delete mode 100644 Sources/Extension/PHAuthorization+BSImagePicker.swift diff --git a/BSImagePicker.xcodeproj/project.pbxproj b/BSImagePicker.xcodeproj/project.pbxproj index 13943d60..5e1eb1de 100644 --- a/BSImagePicker.xcodeproj/project.pbxproj +++ b/BSImagePicker.xcodeproj/project.pbxproj @@ -7,11 +7,9 @@ objects = { /* Begin PBXBuildFile section */ - 2F0FADE226668A74005747B4 /* AutoLayout+BSImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0FADE126668A74005747B4 /* AutoLayout+BSImagePicker.swift */; }; 2F0FADE526669B0F005747B4 /* Size+BSImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0FADE426669B0F005747B4 /* Size+BSImagePicker.swift */; }; 2F0FADE826669B3C005747B4 /* AutorizationStatusHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0FADE726669B3C005747B4 /* AutorizationStatusHeaderView.swift */; }; 2F0FADEC2666A140005747B4 /* Alert+BSImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0FADEB2666A140005747B4 /* Alert+BSImagePicker.swift */; }; - 2F0FADEF2666A185005747B4 /* PHAuthorization+BSImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F0FADEE2666A185005747B4 /* PHAuthorization+BSImagePicker.swift */; }; 531D8C532249C81A00281681 /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531D8C522249C81A00281681 /* SettingsTests.swift */; }; 53FEDA162247FEB80098E34A /* CGSize+Scale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FEDA152247FEB80098E34A /* CGSize+Scale.swift */; }; 53FEDA18224805BA0098E34A /* CGSizeExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FEDA17224805BA0098E34A /* CGSizeExtensionTests.swift */; }; @@ -83,11 +81,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 2F0FADE126668A74005747B4 /* AutoLayout+BSImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AutoLayout+BSImagePicker.swift"; sourceTree = ""; }; 2F0FADE426669B0F005747B4 /* Size+BSImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Size+BSImagePicker.swift"; sourceTree = ""; }; 2F0FADE726669B3C005747B4 /* AutorizationStatusHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutorizationStatusHeaderView.swift; sourceTree = ""; }; 2F0FADEB2666A140005747B4 /* Alert+BSImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Alert+BSImagePicker.swift"; sourceTree = ""; }; - 2F0FADEE2666A185005747B4 /* PHAuthorization+BSImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PHAuthorization+BSImagePicker.swift"; sourceTree = ""; }; 531D8C522249C81A00281681 /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = ""; }; 53FEDA152247FEB80098E34A /* CGSize+Scale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGSize+Scale.swift"; sourceTree = ""; }; 53FEDA17224805BA0098E34A /* CGSizeExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGSizeExtensionTests.swift; sourceTree = ""; }; @@ -348,10 +344,8 @@ isa = PBXGroup; children = ( 94DA686F247BDE5900CD5251 /* UIColor+BSImagePicker.swift */, - 2F0FADE126668A74005747B4 /* AutoLayout+BSImagePicker.swift */, 2F0FADE426669B0F005747B4 /* Size+BSImagePicker.swift */, 2F0FADEB2666A140005747B4 /* Alert+BSImagePicker.swift */, - 2F0FADEE2666A185005747B4 /* PHAuthorization+BSImagePicker.swift */, ); path = Extension; sourceTree = ""; @@ -478,7 +472,6 @@ 55BCF8D521D52C1000386752 /* AssetCollectionViewCell.swift in Sources */, 559DB81B21E6B43400CD58B4 /* ImagePickerController+ButtonActions.swift in Sources */, 2F0FADEC2666A140005747B4 /* Alert+BSImagePicker.swift in Sources */, - 2F0FADE226668A74005747B4 /* AutoLayout+BSImagePicker.swift in Sources */, 559DB81121E6561300CD58B4 /* ImagePickerController+Closure.swift in Sources */, 55C5B802222BD529003CF3F1 /* DropdownPresentationController.swift in Sources */, 55BCF8E021D52C1000386752 /* ZoomAnimator.swift in Sources */, @@ -504,7 +497,6 @@ 55BCF8D721D52C1000386752 /* CheckmarkView.swift in Sources */, 555472AE21E538B000B90CA5 /* AssetsViewController.swift in Sources */, 5543942D232A4EB500DB51B7 /* LivePreviewViewController.swift in Sources */, - 2F0FADEF2666A185005747B4 /* PHAuthorization+BSImagePicker.swift in Sources */, C2DC13CA23F75BDB0035FD13 /* NumberView.swift in Sources */, 55BCF8DC21D52C1000386752 /* PreviewViewController.swift in Sources */, 55BCF8CF21D52C1000386752 /* Settings.swift in Sources */, diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 052e6340..11044662 100644 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -32,6 +32,7 @@ class ViewController: UIViewController { imagePicker.settings.theme.selectionStyle = .numbered imagePicker.settings.fetch.assets.supportedMediaTypes = [.image, .video] imagePicker.settings.selection.unselectOnReachingMax = true + imagePicker.settings.permission.enabled = true let start = Date() self.presentImagePicker(imagePicker, select: { (asset) in diff --git a/Sources/Controller/ImagePickerController.swift b/Sources/Controller/ImagePickerController.swift index 9258fd0d..696821cd 100644 --- a/Sources/Controller/ImagePickerController.swift +++ b/Sources/Controller/ImagePickerController.swift @@ -48,7 +48,6 @@ import Photos var onDeselection: ((_ asset: PHAsset) -> Void)? var onCancel: ((_ assets: [PHAsset]) -> Void)? var onFinish: ((_ assets: [PHAsset]) -> Void)? - var onPermissionChange: ((_ status: PHAuthorizationStatus) -> Void)? let assetsViewController: AssetsViewController let albumsViewController = AlbumsViewController() diff --git a/Sources/Extension/Alert+BSImagePicker.swift b/Sources/Extension/Alert+BSImagePicker.swift index 88718a6c..77ce3f21 100644 --- a/Sources/Extension/Alert+BSImagePicker.swift +++ b/Sources/Extension/Alert+BSImagePicker.swift @@ -24,7 +24,7 @@ import UIKit import Photos extension AssetsViewController { - internal func handleDeniedAccess(){ + internal func showAlertForRestricedOrNotDeterminedAccess(){ let alert = UIAlertController(title: "Allow access to your photos", message: "This lets you share from your camera roll and enables other features for photos. Go to your settings and tap \"Photos\".", preferredStyle: .alert) @@ -43,7 +43,7 @@ extension AssetsViewController { present(alert, animated: true, completion: nil) } - internal func handleLimitedAccess(){ + internal func showAlerForLimitedAccess(){ let actionSheet = UIAlertController(title: "", message: "Select more photos or go to Settings to allow access to all photos.", preferredStyle: .actionSheet) diff --git a/Sources/Extension/AutoLayout+BSImagePicker.swift b/Sources/Extension/AutoLayout+BSImagePicker.swift deleted file mode 100644 index 4d240d04..00000000 --- a/Sources/Extension/AutoLayout+BSImagePicker.swift +++ /dev/null @@ -1,53 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2021 Mithilesh Parmar -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import Foundation - - -@propertyWrapper -public struct UsesAutoLayout { - public var wrappedValue: T { - didSet { - wrappedValue.translatesAutoresizingMaskIntoConstraints = false - } - } - - public init(wrappedValue: T) { - self.wrappedValue = wrappedValue - wrappedValue.translatesAutoresizingMaskIntoConstraints = false - } -} - - -@propertyWrapper -public struct HasUserInteraction { - public var wrappedValue: T { - didSet { - wrappedValue.isUserInteractionEnabled = true - } - } - - public init(wrappedValue: T) { - self.wrappedValue = wrappedValue - wrappedValue.isUserInteractionEnabled = true - } -} diff --git a/Sources/Extension/PHAuthorization+BSImagePicker.swift b/Sources/Extension/PHAuthorization+BSImagePicker.swift deleted file mode 100644 index 29c96718..00000000 --- a/Sources/Extension/PHAuthorization+BSImagePicker.swift +++ /dev/null @@ -1,49 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2021 Mithilesh Parmar -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import UIKit -import Photos - - -extension AssetsViewController { - - internal func checkAuthorizationStatus(){ - - if #available(iOS 14, *) { - PHPhotoLibrary.requestAuthorization(for: .readWrite) { [unowned self] (status) in - DispatchQueue.main.async { - self.dataSource.setStatus(status) - self.collectionView.reloadData() - } - } - }else{ - PHPhotoLibrary.requestAuthorization { (status) in - DispatchQueue.main.async { - self.dataSource.setStatus(status) - self.collectionView.reloadData() - } - } - - } - } - -} diff --git a/Sources/Scene/Assets/AssetsViewController.swift b/Sources/Scene/Assets/AssetsViewController.swift index 7900d789..6fb76a8e 100644 --- a/Sources/Scene/Assets/AssetsViewController.swift +++ b/Sources/Scene/Assets/AssetsViewController.swift @@ -23,7 +23,7 @@ import UIKit import Photos -protocol AssetsViewControllerDelegate: class { +protocol AssetsViewControllerDelegate: AnyObject { func assetsViewController(_ assetsViewController: AssetsViewController, didSelectAsset asset: PHAsset) func assetsViewController(_ assetsViewController: AssetsViewController, didDeselectAsset asset: PHAsset) func assetsViewController(_ assetsViewController: AssetsViewController, didLongPressCell cell: AssetCollectionViewCell, displayingAsset asset: PHAsset) @@ -31,7 +31,7 @@ protocol AssetsViewControllerDelegate: class { class AssetsViewController: UIViewController { weak var delegate: AssetsViewControllerDelegate? - + var settings: Settings! { didSet { dataSource.settings = settings } @@ -89,7 +89,11 @@ class AssetsViewController: UIViewController { collectionView.addGestureRecognizer(longPressRecognizer) if settings.permission.enabled { - checkAuthorizationStatus() + if #available(iOS 14, *) { + dataSource.setStatus(PHPhotoLibrary.authorizationStatus(for: .readWrite)) + } else { + dataSource.setStatus(PHPhotoLibrary.authorizationStatus()) + } } @@ -284,14 +288,16 @@ extension AssetsViewController: PHPhotoLibraryChangeObserver { extension AssetsViewController: AutorizationStatusHeaderViewDelegate { func didTapManageButton(for status: PHAuthorizationStatus) { switch status { - case .denied: - self.handleDeniedAccess() + case .restricted, .notDetermined: + showAlertForRestricedOrNotDeterminedAccess() break case .limited: - self.handleLimitedAccess() - break - default: + showAlerForLimitedAccess() break + // don't do anything if user as authorized access to photos + // not handling .denied case as the controller will not be shown if access is denied + case .authorized, .denied: break + @unknown default: break } } } diff --git a/Sources/Scene/Assets/AutorizationStatusHeaderView.swift b/Sources/Scene/Assets/AutorizationStatusHeaderView.swift index dd11f6b1..223d6472 100644 --- a/Sources/Scene/Assets/AutorizationStatusHeaderView.swift +++ b/Sources/Scene/Assets/AutorizationStatusHeaderView.swift @@ -23,7 +23,7 @@ import UIKit import Photos -protocol AutorizationStatusHeaderViewDelegate: class { +protocol AutorizationStatusHeaderViewDelegate: AnyObject { func didTapManageButton(for status: PHAuthorizationStatus) } @@ -32,16 +32,16 @@ class AutorizationStatusHeaderView : UICollectionReusableView { weak var delegate: AutorizationStatusHeaderViewDelegate? - @UsesAutoLayout - private var titleLabel: UILabel = { + private lazy var titleLabel: UILabel = { let lbl = UILabel() + lbl.translatesAutoresizingMaskIntoConstraints = false lbl.numberOfLines = 3 return lbl }() - @UsesAutoLayout - private var manageButton: UIButton = { + private lazy var manageButton: UIButton = { let btn = UIButton() + btn.translatesAutoresizingMaskIntoConstraints = false return btn }() @@ -57,8 +57,6 @@ class AutorizationStatusHeaderView : UICollectionReusableView { } } - - var permissionDeniedText: String? var permissionDeniedBackgroundColor: UIColor? var permissionDeniedTextColor: UIColor? @@ -114,19 +112,17 @@ class AutorizationStatusHeaderView : UICollectionReusableView { manageButton.topAnchor.constraint(equalTo: self.topAnchor), manageButton.bottomAnchor.constraint(equalTo: self.bottomAnchor), - manageButton.leadingAnchor.constraint(greaterThanOrEqualTo: self.titleLabel.trailingAnchor, constant: 8), manageButton.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16), manageButton.widthAnchor.constraint(greaterThanOrEqualToConstant: manageButton.runtimeSize().width) - ]) } private func addTargets(){ - manageButton.addTarget(self, action: #selector(didTapManage), for: .touchUpInside) + manageButton.addTarget(self, action: #selector(didTapManage(_:)), for: .touchUpInside) } - @objc func didTapManage(){ + @objc func didTapManage(_ sender: UIButton){ guard let status = authorizationStatus else { return } delegate?.didTapManageButton(for: status) } From 5887d42c9c13456024eede8cd6aab077a892d591 Mon Sep 17 00:00:00 2001 From: Mithilesh Parmar Date: Sun, 19 Dec 2021 17:37:05 +0530 Subject: [PATCH 4/9] remove: Development Team from build settings --- Example.xcodeproj/project.pbxproj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Example.xcodeproj/project.pbxproj b/Example.xcodeproj/project.pbxproj index 506bfc3c..1382c432 100644 --- a/Example.xcodeproj/project.pbxproj +++ b/Example.xcodeproj/project.pbxproj @@ -273,7 +273,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = Y2NHS7RD78; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = UITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.4; LD_RUNPATH_SEARCH_PATHS = ( @@ -293,7 +293,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = Y2NHS7RD78; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = UITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.4; LD_RUNPATH_SEARCH_PATHS = ( @@ -345,6 +345,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -407,6 +408,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -432,7 +434,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = F85Q68998X; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Example/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -451,7 +453,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = F85Q68998X; + DEVELOPMENT_TEAM = ""; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Example/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( From 994b816538661faca7f82f979505af242a1361b5 Mon Sep 17 00:00:00 2001 From: Mithilesh Parmar Date: Sun, 19 Dec 2021 17:38:35 +0530 Subject: [PATCH 5/9] replace: Development team credentials to default credentials --- Example.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Example.xcodeproj/project.pbxproj b/Example.xcodeproj/project.pbxproj index 1382c432..96f44749 100644 --- a/Example.xcodeproj/project.pbxproj +++ b/Example.xcodeproj/project.pbxproj @@ -273,7 +273,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = Y2NHS7RD78; INFOPLIST_FILE = UITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.4; LD_RUNPATH_SEARCH_PATHS = ( @@ -293,7 +293,7 @@ isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = Y2NHS7RD78; INFOPLIST_FILE = UITests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.4; LD_RUNPATH_SEARCH_PATHS = ( @@ -345,7 +345,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = Y2NHS7RD78; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -408,7 +408,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = Y2NHS7RD78; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -434,7 +434,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = Y2NHS7RD78; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Example/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -453,7 +453,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = Y2NHS7RD78; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Example/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( From 8cacffffd53ec3a1b8e074353135749a6dd2e44b Mon Sep 17 00:00:00 2001 From: Mithilesh Parmar Date: Sun, 19 Dec 2021 17:40:14 +0530 Subject: [PATCH 6/9] replace: bundle identifier of example with default identifier --- Example.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Example.xcodeproj/project.pbxproj b/Example.xcodeproj/project.pbxproj index 96f44749..cdb254d5 100644 --- a/Example.xcodeproj/project.pbxproj +++ b/Example.xcodeproj/project.pbxproj @@ -441,7 +441,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.Example.mithilesh; + PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.Example; PRODUCT_NAME = ImagePicker; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -460,7 +460,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.Example.mithilesh; + PRODUCT_BUNDLE_IDENTIFIER = se.backslashed.Example; PRODUCT_NAME = ImagePicker; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; From 01220e06a2489cc4f7d4ef87489a014ad242af48 Mon Sep 17 00:00:00 2001 From: Mithilesh Parmar Date: Mon, 20 Dec 2021 16:54:56 +0530 Subject: [PATCH 7/9] add: Localized strings for alerts --- Sources/Extension/Alert+BSImagePicker.swift | 37 ++++++++++--------- Sources/Model/Settings.swift | 40 ++++++++++----------- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/Sources/Extension/Alert+BSImagePicker.swift b/Sources/Extension/Alert+BSImagePicker.swift index 77ce3f21..28e9261e 100644 --- a/Sources/Extension/Alert+BSImagePicker.swift +++ b/Sources/Extension/Alert+BSImagePicker.swift @@ -25,17 +25,18 @@ import Photos extension AssetsViewController { internal func showAlertForRestricedOrNotDeterminedAccess(){ - let alert = UIAlertController(title: "Allow access to your photos", - message: "This lets you share from your camera roll and enables other features for photos. Go to your settings and tap \"Photos\".", - preferredStyle: .alert) + + let localizedTitle = NSLocalizedString("bsimagepicker.restrictedAccess.alert.title", comment: "Allow access to your photos") + let localizedMessage = NSLocalizedString("bsimagepicker.restrictedAccess.alert.message", comment: "This lets you share from your camera roll and enables other features for photos. Go to your settings and tap \"Photos\".") + let notNowLocalizedText = NSLocalizedString("bsimagepicker.restrictedAccess.alert.secondaryButton.title", comment: "Not now") + let openSettinsgLocalizedText = NSLocalizedString("bsimagepicker.restrictedAccess.alert.openSettings.title", comment: "Open Settings") + + let alert = UIAlertController(title: localizedTitle, message: localizedMessage, preferredStyle: .alert) - let notNowAction = UIAlertAction(title: "Not Now", - style: .cancel, - handler: nil) + let notNowAction = UIAlertAction(title: notNowLocalizedText, style: .cancel, handler: nil) alert.addAction(notNowAction) - let openSettingsAction = UIAlertAction(title: "Open Settings", - style: .default) { [unowned self] (_) in + let openSettingsAction = UIAlertAction(title: openSettinsgLocalizedText, style: .default) { [unowned self] (_) in // Open app privacy settings gotoAppPrivacySettings() } @@ -44,12 +45,17 @@ extension AssetsViewController { } internal func showAlerForLimitedAccess(){ - let actionSheet = UIAlertController(title: "", - message: "Select more photos or go to Settings to allow access to all photos.", - preferredStyle: .actionSheet) - let selectPhotosAction = UIAlertAction(title: "Select more photos", - style: .default) { [unowned self] (_) in + let localizedTitle = NSLocalizedString("bsimagepicker.limitedAccess.alert.title", comment: "") + let localizedMessage = NSLocalizedString("bsimagepicker.limitedAccess.alert.message", comment: "Select more photos or go to Settings to allow access to all photos.") + let selectMorePhotosLocalizedText = NSLocalizedString("bsimagepicker.limitedAccess.alert.selectMorePhotos", comment: "Select more photos") + let allowAccessToAllPhotosLocalizedText = NSLocalizedString("bsimagepicker.limitedAccess.alert.allowAccessToAllPhotos", comment: "Allow access to all photos") + + let cancelLocalizedText = NSLocalizedString("bsimagepicker.cancel", comment: "Cancel") + + let actionSheet = UIAlertController(title: localizedTitle, message: localizedMessage, preferredStyle: .actionSheet) + + let selectPhotosAction = UIAlertAction(title: selectMorePhotosLocalizedText, style: .default) { [unowned self] (_) in // Show limited library picker if #available(iOS 14, *) { PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: self) @@ -57,14 +63,13 @@ extension AssetsViewController { } actionSheet.addAction(selectPhotosAction) - let allowFullAccessAction = UIAlertAction(title: "Allow access to all photos", - style: .default) { [unowned self] (_) in + let allowFullAccessAction = UIAlertAction(title: allowAccessToAllPhotosLocalizedText, style: .default) { [unowned self] (_) in // Open app privacy settings self.gotoAppPrivacySettings() } actionSheet.addAction(allowFullAccessAction) - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + let cancelAction = UIAlertAction(title: cancelLocalizedText, style: .cancel, handler: nil) actionSheet.addAction(cancelAction) present(actionSheet, animated: true, completion: nil) diff --git a/Sources/Model/Settings.swift b/Sources/Model/Settings.swift index f4cf4744..52298cbd 100755 --- a/Sources/Model/Settings.swift +++ b/Sources/Model/Settings.swift @@ -26,7 +26,7 @@ import Photos @objc(BSImagePickerSettings) // Fix for ObjC header name conflicting. @objcMembers public class Settings : NSObject { public static let shared = Settings() - + // Move all theme related stuff to UIAppearance public class Theme : NSObject { /// Main background color @@ -79,7 +79,7 @@ import Photos /// If it reaches the max limit, unselect the first selection, and allow the new selection @objc public lazy var unselectOnReachingMax : Bool = false } - + @objc(BSImagePickerList) @objcMembers public class List : NSObject { /// How much spacing between cells @@ -99,12 +99,12 @@ import Photos } } } - + public class Preview : NSObject { /// Is preview enabled? public lazy var enabled: Bool = true } - + @objc(BSImagePickerFetch) @objcMembers public class Fetch : NSObject { @objc(BSImagePickerAlbum) @@ -114,7 +114,7 @@ import Photos let fetchOptions = PHFetchOptions() return fetchOptions }() - + /// Fetch results for asset collections you want to present to the user /// Some other fetch results that you might wanna use: /// PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumFavorites, options: options), @@ -126,16 +126,16 @@ import Photos PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: options), ] } - + @objc(BSImagePickerAssets) @objcMembers public class Assets : NSObject { /// Fetch options for assets - + /// Simple wrapper around PHAssetMediaType to ensure we only expose the supported types. public enum MediaTypes { case image case video - + fileprivate var assetMediaType: PHAssetMediaType { switch self { case .image: @@ -146,48 +146,48 @@ import Photos } } public lazy var supportedMediaTypes: Set = [.image] - + public lazy var options: PHFetchOptions = { let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [ NSSortDescriptor(key: "creationDate", ascending: false) ] - + let rawMediaTypes = supportedMediaTypes.map { $0.assetMediaType.rawValue } let predicate = NSPredicate(format: "mediaType IN %@", rawMediaTypes) fetchOptions.predicate = predicate - + return fetchOptions }() } - + public class Preview : NSObject { public lazy var photoOptions: PHImageRequestOptions = { let options = PHImageRequestOptions() options.isNetworkAccessAllowed = true - + return options }() - + public lazy var livePhotoOptions: PHLivePhotoRequestOptions = { let options = PHLivePhotoRequestOptions() options.isNetworkAccessAllowed = true return options }() - + public lazy var videoOptions: PHVideoRequestOptions = { let options = PHVideoRequestOptions() options.isNetworkAccessAllowed = true return options }() } - + /// Album fetch settings public lazy var album = Album() /// Asset fetch settings public lazy var assets = Assets() - + /// Preview fetch settings public lazy var preview = Preview() } @@ -195,7 +195,7 @@ import Photos public class Dismiss : NSObject { /// Should the image picker dismiss when done/cancelled public lazy var enabled = true - + /// Allow the user to dismiss the image picker by swiping down public lazy var allowSwipe = false } @@ -216,7 +216,7 @@ import Photos public lazy var manageButtonText: String = "Manage" public lazy var manageButtonTextColor: UIColor = .systemBlue } - + /// Theme settings public lazy var theme = Theme() @@ -231,7 +231,7 @@ import Photos /// Dismiss settings public lazy var dismiss = Dismiss() - + /// Preview options public lazy var preview = Preview() From dff1e7b183689cd5853c046df57743af24f75b70 Mon Sep 17 00:00:00 2001 From: Mithilesh Parmar Date: Mon, 20 Dec 2021 17:32:36 +0530 Subject: [PATCH 8/9] add: default values for strings --- Sources/Extension/Alert+BSImagePicker.swift | 50 ++++++++++++++----- Sources/Model/Settings.swift | 15 +++--- .../AssetsCollectionViewDataSource.swift | 30 +++++++---- .../Assets/AutorizationStatusHeaderView.swift | 46 ++--------------- 4 files changed, 69 insertions(+), 72 deletions(-) diff --git a/Sources/Extension/Alert+BSImagePicker.swift b/Sources/Extension/Alert+BSImagePicker.swift index 28e9261e..b98ed70c 100644 --- a/Sources/Extension/Alert+BSImagePicker.swift +++ b/Sources/Extension/Alert+BSImagePicker.swift @@ -25,11 +25,22 @@ import Photos extension AssetsViewController { internal func showAlertForRestricedOrNotDeterminedAccess(){ - - let localizedTitle = NSLocalizedString("bsimagepicker.restrictedAccess.alert.title", comment: "Allow access to your photos") - let localizedMessage = NSLocalizedString("bsimagepicker.restrictedAccess.alert.message", comment: "This lets you share from your camera roll and enables other features for photos. Go to your settings and tap \"Photos\".") - let notNowLocalizedText = NSLocalizedString("bsimagepicker.restrictedAccess.alert.secondaryButton.title", comment: "Not now") - let openSettinsgLocalizedText = NSLocalizedString("bsimagepicker.restrictedAccess.alert.openSettings.title", comment: "Open Settings") + + let localizedTitle = NSLocalizedString("bsimagepicker.restrictedAccess.alert.title", + value: "Allow access to your photos", + comment: "Title of alert controller") + + let localizedMessage = NSLocalizedString("bsimagepicker.restrictedAccess.alert.message", + value: "This lets you share from your camera roll and enables other features for photos. Go to your settings and tap \"Photos\".", + comment: "Message of alert controller") + + let notNowLocalizedText = NSLocalizedString("bsimagepicker.restrictedAccess.alert.secondaryButton.title", + value: "Not now", + comment: "Secondary button title") + + let openSettinsgLocalizedText = NSLocalizedString("bsimagepicker.restrictedAccess.alert.openSettings.title", + value: "Open Settings", + comment: "Primart button title") let alert = UIAlertController(title: localizedTitle, message: localizedMessage, preferredStyle: .alert) @@ -46,12 +57,25 @@ extension AssetsViewController { internal func showAlerForLimitedAccess(){ - let localizedTitle = NSLocalizedString("bsimagepicker.limitedAccess.alert.title", comment: "") - let localizedMessage = NSLocalizedString("bsimagepicker.limitedAccess.alert.message", comment: "Select more photos or go to Settings to allow access to all photos.") - let selectMorePhotosLocalizedText = NSLocalizedString("bsimagepicker.limitedAccess.alert.selectMorePhotos", comment: "Select more photos") - let allowAccessToAllPhotosLocalizedText = NSLocalizedString("bsimagepicker.limitedAccess.alert.allowAccessToAllPhotos", comment: "Allow access to all photos") + let localizedTitle = NSLocalizedString("bsimagepicker.limitedAccess.alert.title", + value: " ", + comment: "Title of Alert controller") - let cancelLocalizedText = NSLocalizedString("bsimagepicker.cancel", comment: "Cancel") + let localizedMessage = NSLocalizedString("bsimagepicker.limitedAccess.alert.message", + value: "Select more photos or go to Settings to allow access to all photos.", + comment: "Message of Alert controller") + + let selectMorePhotosLocalizedText = NSLocalizedString("bsimagepicker.limitedAccess.alert.selectMorePhotos", + value: "Select more photos", + comment: "Select more photos action title") + + let allowAccessToAllPhotosLocalizedText = NSLocalizedString("bsimagepicker.limitedAccess.alert.allowAccessToAllPhotos", + value: "Allow access to all photos", + comment: "Allow access to all photos action title") + + let cancelLocalizedText = NSLocalizedString("bsimagepicker.cancel", + value: "Cancel", + comment: "Cancel action title") let actionSheet = UIAlertController(title: localizedTitle, message: localizedMessage, preferredStyle: .actionSheet) @@ -79,9 +103,9 @@ extension AssetsViewController { internal func gotoAppPrivacySettings() { guard let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) else { - assertionFailure("Not able to open App privacy settings") - return - } + assertionFailure("Not able to open App privacy settings") + return + } UIApplication.shared.open(url, options: [:], completionHandler: nil) } diff --git a/Sources/Model/Settings.swift b/Sources/Model/Settings.swift index 52298cbd..0e10fd20 100755 --- a/Sources/Model/Settings.swift +++ b/Sources/Model/Settings.swift @@ -204,16 +204,17 @@ import Photos /// should the image picker check for permission when loaded and give callbacks public lazy var enabled = false - public lazy var permissionDeniedWarningText: String = "Permission is required to access photos library." - public lazy var permissionDeniedHeaderBackgroundColor: UIColor = .clear - public lazy var permissionDeniedWarningTextColor: UIColor = .red + public var limitedPermissionHeaderTitle: String = NSLocalizedString("bsimagepicker.permissionHeader.title", value: "You've given access to only select number of photos.", comment: "Title of header when access is limited") + public var limitedPermissionHeaderBackgroundColor: UIColor = .clear + public var limitedPermissionHeaderTitleColor: UIColor = .systemPrimaryTextColor - public lazy var limitedPermissionWarningText: String = "You've given access to only select number of photos." - public lazy var limitedPermissionHeaderBackgroundColor: UIColor = .clear - public lazy var limitedPermissionWarningTextColor: UIColor = .systemPrimaryTextColor + public var restrictedOrNotDeterminedPermissionHeaderTitle: String = NSLocalizedString("bsimagepicker.permissionHeader.title", value: "You've given access to only select number of photos.", comment: "Title of header when access is restricted or not determined") + public var restrictedOrNotDeterminedPermissionHeaderBackgroundColor: UIColor = .clear + public var restrictedOrNotDeterminedPermissionHeaderTitleColor: UIColor = .systemPrimaryTextColor - public lazy var manageButtonText: String = "Manage" + + public lazy var manageButtonText: String = NSLocalizedString("bsimagepicker.permissionHeader.button.title", value: "Manage", comment: "Title of the button in header view") public lazy var manageButtonTextColor: UIColor = .systemBlue } diff --git a/Sources/Scene/Assets/AssetsCollectionViewDataSource.swift b/Sources/Scene/Assets/AssetsCollectionViewDataSource.swift index ef5a0d33..07aee761 100755 --- a/Sources/Scene/Assets/AssetsCollectionViewDataSource.swift +++ b/Sources/Scene/Assets/AssetsCollectionViewDataSource.swift @@ -119,16 +119,28 @@ class AssetsCollectionViewDataSource : NSObject, UICollectionViewDataSource, UIC let cell = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: AutorizationStatusHeaderView.id, for: indexPath) as! AutorizationStatusHeaderView cell.delegate = headerViewDelegate - cell.buttonText = settings.permission.manageButtonText - cell.buttonTextColor = settings.permission.manageButtonTextColor + cell.manageButton.setTitle(settings.permission.manageButtonText, for: .normal) + cell.manageButton.setTitleColor(settings.permission.manageButtonTextColor, for: .normal) - cell.limitedPermissionBackgroundColor = settings.permission.limitedPermissionHeaderBackgroundColor - cell.limitedPermissionGrantedText = settings.permission.limitedPermissionWarningText - cell.limitedPermissionTextColor = settings.permission.limitedPermissionWarningTextColor - - cell.permissionDeniedText = settings.permission.permissionDeniedWarningText - cell.permissionDeniedBackgroundColor = settings.permission.permissionDeniedHeaderBackgroundColor - cell.permissionDeniedTextColor = settings.permission.permissionDeniedWarningTextColor + if let authorizationStatus = authorizationStatus { + switch authorizationStatus { + case .limited: + cell.titleLabel.text = settings.permission.limitedPermissionHeaderTitle + cell.titleLabel.textColor = settings.permission.limitedPermissionHeaderTitleColor + cell.backgroundColor = settings.permission.limitedPermissionHeaderBackgroundColor + cell.layoutIfNeeded() + break + case .restricted, .notDetermined: + cell.titleLabel.text = settings.permission.restrictedOrNotDeterminedPermissionHeaderTitle + cell.titleLabel.textColor = settings.permission.restrictedOrNotDeterminedPermissionHeaderTitleColor + cell.backgroundColor = settings.permission.restrictedOrNotDeterminedPermissionHeaderBackgroundColor + cell.layoutIfNeeded() + case .authorized, .denied: + break + @unknown default: + break + } + } cell.authorizationStatus = authorizationStatus return cell diff --git a/Sources/Scene/Assets/AutorizationStatusHeaderView.swift b/Sources/Scene/Assets/AutorizationStatusHeaderView.swift index 223d6472..40405530 100644 --- a/Sources/Scene/Assets/AutorizationStatusHeaderView.swift +++ b/Sources/Scene/Assets/AutorizationStatusHeaderView.swift @@ -32,60 +32,20 @@ class AutorizationStatusHeaderView : UICollectionReusableView { weak var delegate: AutorizationStatusHeaderViewDelegate? - private lazy var titleLabel: UILabel = { + private(set) lazy var titleLabel: UILabel = { let lbl = UILabel() lbl.translatesAutoresizingMaskIntoConstraints = false lbl.numberOfLines = 3 return lbl }() - private lazy var manageButton: UIButton = { + private(set) lazy var manageButton: UIButton = { let btn = UIButton() btn.translatesAutoresizingMaskIntoConstraints = false return btn }() - var buttonText: String? { - didSet{ - manageButton.setTitle(buttonText, for: .normal) - } - } - - var buttonTextColor: UIColor? { - didSet{ - manageButton.setTitleColor(buttonTextColor, for: .normal) - } - } - - var permissionDeniedText: String? - var permissionDeniedBackgroundColor: UIColor? - var permissionDeniedTextColor: UIColor? - - var limitedPermissionGrantedText: String? - var limitedPermissionBackgroundColor: UIColor? - var limitedPermissionTextColor: UIColor? - - var authorizationStatus: PHAuthorizationStatus?{ - didSet { - guard let status = authorizationStatus else { return } - switch status { - case .denied: - titleLabel.text = permissionDeniedText - titleLabel.textColor = permissionDeniedTextColor - backgroundColor = permissionDeniedBackgroundColor - layoutIfNeeded() - break - case .limited: - titleLabel.text = limitedPermissionGrantedText - titleLabel.textColor = limitedPermissionTextColor - backgroundColor = limitedPermissionBackgroundColor - layoutIfNeeded() - break - default: - break - } - } - } + var authorizationStatus: PHAuthorizationStatus? override init(frame: CGRect) { super.init(frame: frame) From 8e9eb1c96ba31964448b1d95124a6edf18f43650 Mon Sep 17 00:00:00 2001 From: Mithilesh Parmar Date: Mon, 20 Dec 2021 17:40:38 +0530 Subject: [PATCH 9/9] add: Localized strings keys as comments in settings class --- Sources/Model/Settings.swift | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/Sources/Model/Settings.swift b/Sources/Model/Settings.swift index 0e10fd20..cb713190 100755 --- a/Sources/Model/Settings.swift +++ b/Sources/Model/Settings.swift @@ -202,14 +202,32 @@ import Photos public class Permission: NSObject { /// should the image picker check for permission when loaded and give callbacks + + /// Localizable string keys + + /// bsimagepicker.limitedPermissionHeader.title -> Header title when access to photos is limited + /// bsimagepicker.restrictedOrNotDeterminedpermissionHeader.title -> Header title when access to photos is restricted or not determined + /// bsimagepicker.permissionHeader.button.title -> Button title of Header view + + /// bsimagepicker.limitedAccess.alert.title -> Title of alert controller when access is Limited + /// bsimagepicker.limitedAccess.alert.message -> Message of alert controller when access is Limited + /// bsimagepicker.limitedAccess.alert.selectMorePhotos -> Title of Select more photos action of alert controller when access is Limited + /// bsimagepicker.limitedAccess.alert.allowAccessToAllPhotos -> Title of Allow access to all photos of alert controller when access is Limited + /// bsimagepicker.cancel -> Title of cancel action of alert controller when access is Limited + + /// bsimagepicker.restrictedAccess.alert.title -> Title of alert controller when access is Restricted or not determined + /// bsimagepicker.restrictedAccess.alert.message -> Message of alert controller when access is Restricted or not determined + /// bsimagepicker.restrictedAccess.alert.secondaryButton.title -> Title of secondary action of alert controller when access is Restricted or not determined + /// bsimagepicker.restrictedAccess.alert.openSettings.title -> Title of primary action of alert controller when access is Restricted or not determined + public lazy var enabled = false - public var limitedPermissionHeaderTitle: String = NSLocalizedString("bsimagepicker.permissionHeader.title", value: "You've given access to only select number of photos.", comment: "Title of header when access is limited") + public var limitedPermissionHeaderTitle: String = NSLocalizedString("bsimagepicker.limitedPermissionHeader.title", value: "You've given access to only select number of photos.", comment: "Title of header when access is limited") public var limitedPermissionHeaderBackgroundColor: UIColor = .clear public var limitedPermissionHeaderTitleColor: UIColor = .systemPrimaryTextColor - public var restrictedOrNotDeterminedPermissionHeaderTitle: String = NSLocalizedString("bsimagepicker.permissionHeader.title", value: "You've given access to only select number of photos.", comment: "Title of header when access is restricted or not determined") + public var restrictedOrNotDeterminedPermissionHeaderTitle: String = NSLocalizedString("bsimagepicker.restrictedOrNotDeterminedpermissionHeader.title", value: "You've given access to only select number of photos.", comment: "Title of header when access is restricted or not determined") public var restrictedOrNotDeterminedPermissionHeaderBackgroundColor: UIColor = .clear public var restrictedOrNotDeterminedPermissionHeaderTitleColor: UIColor = .systemPrimaryTextColor