diff --git a/Source/Helpers/Extensions/Array+Extensions.swift b/Source/Helpers/Extensions/Array+Extensions.swift new file mode 100644 index 000000000..7282b2b9d --- /dev/null +++ b/Source/Helpers/Extensions/Array+Extensions.swift @@ -0,0 +1,13 @@ +// +// Array+Extensions.swift +// YPImagePicker +// +// Created by Nik Kov on 06.01.2022. +// Copyright © 2022 Yummypets. All rights reserved. +// + +internal extension Array { + subscript (safe index: Index) -> Element? { + return indices.contains(index) ? self[index] : nil + } +} diff --git a/Source/Pages/Gallery/LibraryMediaManager.swift b/Source/Pages/Gallery/LibraryMediaManager.swift index 856ed8c76..9b10a56ea 100644 --- a/Source/Pages/Gallery/LibraryMediaManager.swift +++ b/Source/Pages/Gallery/LibraryMediaManager.swift @@ -13,7 +13,7 @@ class LibraryMediaManager { weak var v: YPLibraryView? var collection: PHAssetCollection? - internal var fetchResult: PHFetchResult! + internal var fetchResult: PHFetchResult? internal var previousPreheatRect: CGRect = .zero internal var imageManager: PHCachingImageManager? internal var exportTimer: Timer? @@ -60,8 +60,11 @@ class LibraryMediaManager { addedIndexPaths += indexPaths }) - let assetsToStartCaching = fetchResult.assetsAtIndexPaths(addedIndexPaths) - let assetsToStopCaching = fetchResult.assetsAtIndexPaths(removedIndexPaths) + guard let assetsToStartCaching = fetchResult?.assetsAtIndexPaths(addedIndexPaths), + let assetsToStopCaching = fetchResult?.assetsAtIndexPaths(removedIndexPaths) else { + print("Some problems in fetching and caching assets.") + return + } imageManager?.startCachingImages(for: assetsToStartCaching, targetSize: cellSize, @@ -215,4 +218,16 @@ class LibraryMediaManager { s.cancelExport() } } + + func getAsset(at index: Int) -> PHAsset? { + guard let fetchResult = fetchResult else { + print("FetchResult not contain this index: \(index)") + return nil + } + guard fetchResult.count > index else { + print("FetchResult not contain this index: \(index)") + return nil + } + return fetchResult.object(at: index) + } } diff --git a/Source/Pages/Gallery/YPLibrary+LibraryChange.swift b/Source/Pages/Gallery/YPLibrary+LibraryChange.swift index 8b0991ca4..8bbbf2fd6 100644 --- a/Source/Pages/Gallery/YPLibrary+LibraryChange.swift +++ b/Source/Pages/Gallery/YPLibrary+LibraryChange.swift @@ -57,16 +57,16 @@ extension YPLibraryVC: PHPhotoLibraryChangeObserver { // If no items selected in assetView, but there are already photos // after photoLibraryDidChange, than select first item in library. // It can be when user add photos from limited permission. - if self.mediaManager.fetchResult.count > 0, - selectedItems.isEmpty { - let newAsset = self.mediaManager.fetchResult[0] + if self.mediaManager.hasResultItems, + selectedItems.isEmpty, + let newAsset = self.mediaManager.getAsset(at: 0) { self.changeAsset(newAsset) } // If user decided to forbid all photos with limited permission // while using the lib we need to remove asset from assets view. if selectedItems.isEmpty == false, - self.mediaManager.fetchResult.count == 0 { + self.mediaManager.hasResultItems == false { self.v.assetZoomableView.clearAsset() self.selectedItems.removeAll() self.delegate?.libraryViewFinishedLoading() diff --git a/Source/Pages/Gallery/YPLibraryVC+CollectionView.swift b/Source/Pages/Gallery/YPLibraryVC+CollectionView.swift index 1b17cd385..89e6ac411 100644 --- a/Source/Pages/Gallery/YPLibraryVC+CollectionView.swift +++ b/Source/Pages/Gallery/YPLibraryVC+CollectionView.swift @@ -24,7 +24,7 @@ extension YPLibraryVC { /// When tapping on the cell with long press, clear all previously selected cells. @objc func handleLongPress(longPressGR: UILongPressGestureRecognizer) { - if multipleSelectionEnabled || isProcessing || YPConfig.library.maxNumberOfItems <= 1 { + if isMultipleSelectionEnabled || isProcessing || YPConfig.library.maxNumberOfItems <= 1 { return } @@ -39,11 +39,11 @@ extension YPLibraryVC { func startMultipleSelection(at indexPath: IndexPath) { currentlySelectedIndex = indexPath.row - multipleSelectionButtonTapped() + toggleMultipleSelection() // Update preview. - changeAsset(mediaManager.fetchResult[indexPath.row]) - + changeAsset(mediaManager.getAsset(at: indexPath.row)) + // Bring preview down and keep selected cell visible. panGestureHelper.resetToOriginalState() if !panGestureHelper.isImageShown { @@ -57,7 +57,7 @@ extension YPLibraryVC { /// Removes cell from selection func deselect(indexPath: IndexPath) { if let positionIndex = selectedItems.firstIndex(where: { - $0.assetIdentifier == mediaManager.fetchResult[indexPath.row].localIdentifier + $0.assetIdentifier == mediaManager.getAsset(at: indexPath.row)?.localIdentifier }) { selectedItems.remove(at: positionIndex) @@ -70,7 +70,7 @@ extension YPLibraryVC { v.collectionView.deselectItem(at: indexPath, animated: false) v.collectionView.selectItem(at: previouslySelectedIndexPath, animated: false, scrollPosition: []) currentlySelectedIndex = previouslySelectedIndexPath.row - changeAsset(mediaManager.fetchResult[previouslySelectedIndexPath.row]) + changeAsset(mediaManager.getAsset(at: previouslySelectedIndexPath.row)) } checkLimit() @@ -83,8 +83,11 @@ extension YPLibraryVC { numSelections: selectedItems.count) ?? true) { return } - - let asset = mediaManager.fetchResult[indexPath.item] + guard let asset = mediaManager.getAsset(at: indexPath.item) else { + print("No asset to add to selection.") + return + } + let newSelection = YPLibrarySelection(index: indexPath.row, assetIdentifier: asset.localIdentifier) selectedItems.append(newSelection) checkLimit() @@ -92,19 +95,19 @@ extension YPLibraryVC { func isInSelectionPool(indexPath: IndexPath) -> Bool { return selectedItems.contains(where: { - $0.assetIdentifier == mediaManager.fetchResult[indexPath.row].localIdentifier + $0.assetIdentifier == mediaManager.getAsset(at: indexPath.row)?.localIdentifier }) } /// Checks if there can be selected more items. If no - present warning. func checkLimit() { - v.maxNumberWarningView.isHidden = !isLimitExceeded || multipleSelectionEnabled == false + v.maxNumberWarningView.isHidden = !isLimitExceeded || isMultipleSelectionEnabled == false } } extension YPLibraryVC: UICollectionViewDataSource { public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return mediaManager.fetchResult.count + return mediaManager.fetchResult?.count ?? 0 } } @@ -112,11 +115,13 @@ extension YPLibraryVC: UICollectionViewDelegate { public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let asset = mediaManager.fetchResult[indexPath.item] - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YPLibraryViewCell", - for: indexPath) as? YPLibraryViewCell else { - fatalError("unexpected cell in collection view") + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YPLibraryViewCell", for: indexPath) as? YPLibraryViewCell else { + fatalError("unexpected cell in collection view") } + guard let asset = mediaManager.getAsset(at: indexPath.item) else { + return cell + } + cell.representedAssetIdentifier = asset.localIdentifier cell.multipleSelectionIndicator.selectionColor = YPConfig.colors.multipleItemsSelectedCircleColor ?? YPConfig.colors.tintColor @@ -134,7 +139,7 @@ extension YPLibraryVC: UICollectionViewDelegate { let isVideo = (asset.mediaType == .video) cell.durationLabel.isHidden = !isVideo cell.durationLabel.text = isVideo ? YPHelper.formattedStrigFrom(asset.duration) : "" - cell.multipleSelectionIndicator.isHidden = !multipleSelectionEnabled + cell.multipleSelectionIndicator.isHidden = !isMultipleSelectionEnabled cell.isSelected = currentlySelectedIndex == indexPath.row // Set correct selection number @@ -163,7 +168,7 @@ extension YPLibraryVC: UICollectionViewDelegate { let previouslySelectedIndexPath = IndexPath(row: currentlySelectedIndex, section: 0) currentlySelectedIndex = indexPath.row - changeAsset(mediaManager.fetchResult[indexPath.row]) + changeAsset(mediaManager.getAsset(at: indexPath.row)) panGestureHelper.resetToOriginalState() // Only scroll cell to top if preview is hidden. @@ -172,7 +177,7 @@ extension YPLibraryVC: UICollectionViewDelegate { } v.refreshImageCurtainAlpha() - if multipleSelectionEnabled { + if isMultipleSelectionEnabled { let cellIsInTheSelectionPool = isInSelectionPool(indexPath: indexPath) let cellIsCurrentlySelected = previouslySelectedIndexPath.row == currentlySelectedIndex if cellIsInTheSelectionPool { diff --git a/Source/Pages/Gallery/YPLibraryVC.swift b/Source/Pages/Gallery/YPLibraryVC.swift index 18bb7d2f1..53b5451f1 100644 --- a/Source/Pages/Gallery/YPLibraryVC.swift +++ b/Source/Pages/Gallery/YPLibraryVC.swift @@ -16,7 +16,7 @@ internal final class YPLibraryVC: UIViewController, YPPermissionCheckable { internal var isProcessing = false // true if video or image is in processing state internal var selectedItems = [YPLibrarySelection]() internal let mediaManager = LibraryMediaManager() - internal var multipleSelectionEnabled = false + internal var isMultipleSelectionEnabled = false internal var currentlySelectedIndex: Int = 0 internal let panGestureHelper = PanGestureHelper() internal var isInitialized = false @@ -75,7 +75,7 @@ internal final class YPLibraryVC: UIViewController, YPPermissionCheckable { // The negative index will be corrected in the collectionView:cellForItemAt: return YPLibrarySelection(index: -1, assetIdentifier: asset.localIdentifier) } - v.assetViewContainer.setMultipleSelectionMode(on: multipleSelectionEnabled) + v.assetViewContainer.setMultipleSelectionMode(on: isMultipleSelectionEnabled) v.collectionView.reloadData() } @@ -84,7 +84,7 @@ internal final class YPLibraryVC: UIViewController, YPPermissionCheckable { } if YPConfig.library.defaultMultipleSelection || selectedItems.count > 1 { - showMultipleSelection() + toggleMultipleSelection() } } @@ -92,7 +92,7 @@ internal final class YPLibraryVC: UIViewController, YPPermissionCheckable { title = album.title mediaManager.collection = album.collection currentlySelectedIndex = 0 - if !multipleSelectionEnabled { + if !isMultipleSelectionEnabled { selectedItems.removeAll() } refreshMediaRequest() @@ -171,27 +171,28 @@ internal final class YPLibraryVC: UIViewController, YPPermissionCheckable { } doAfterLibraryPermissionCheck { [weak self] in - if self?.multipleSelectionEnabled == false { + if self?.isMultipleSelectionEnabled == false { self?.selectedItems.removeAll() } - self?.showMultipleSelection() + self?.toggleMultipleSelection() } } - func showMultipleSelection() { + func toggleMultipleSelection() { // Prevent desactivating multiple selection when using `minNumberOfItems` - if YPConfig.library.minNumberOfItems > 1 && multipleSelectionEnabled { - print("Selected minNumberOfItems greater than one :\(YPConfig.library.minNumberOfItems). Don't deselect multiple selection.") + if YPConfig.library.minNumberOfItems > 1 && isMultipleSelectionEnabled { + print("Selected minNumberOfItems greater than one :\(YPConfig.library.minNumberOfItems). Don't deselecting multiple selection.") return } - - multipleSelectionEnabled = !multipleSelectionEnabled - - if multipleSelectionEnabled { - if selectedItems.isEmpty && YPConfig.library.preSelectItemOnMultipleSelection, - delegate?.libraryViewShouldAddToSelection(indexPath: IndexPath(row: currentlySelectedIndex, section: 0), - numSelections: selectedItems.count) ?? true { - let asset = mediaManager.fetchResult[currentlySelectedIndex] + + isMultipleSelectionEnabled.toggle() + + if isMultipleSelectionEnabled { + let needPreselectItemsAndNotSelectedAnyYet = selectedItems.isEmpty && YPConfig.library.preSelectItemOnMultipleSelection + let shouldSelectByDelegate = delegate?.libraryViewShouldAddToSelection(indexPath: IndexPath(row: currentlySelectedIndex, section: 0), numSelections: selectedItems.count) ?? true + if needPreselectItemsAndNotSelectedAnyYet, + shouldSelectByDelegate, + let asset = mediaManager.getAsset(at: currentlySelectedIndex) { selectedItems = [ YPLibrarySelection(index: currentlySelectedIndex, cropRect: v.currentCropRect(), @@ -205,10 +206,10 @@ internal final class YPLibraryVC: UIViewController, YPPermissionCheckable { addToSelection(indexPath: IndexPath(row: currentlySelectedIndex, section: 0)) } - v.assetViewContainer.setMultipleSelectionMode(on: multipleSelectionEnabled) + v.assetViewContainer.setMultipleSelectionMode(on: isMultipleSelectionEnabled) v.collectionView.reloadData() checkLimit() - delegate?.libraryViewDidToggleMultipleSelection(enabled: multipleSelectionEnabled) + delegate?.libraryViewDidToggleMultipleSelection(enabled: isMultipleSelectionEnabled) } // MARK: - Tap Preview @@ -236,13 +237,14 @@ internal final class YPLibraryVC: UIViewController, YPPermissionCheckable { mediaManager.fetchResult = PHAsset.fetchAssets(with: options) } - if mediaManager.hasResultItems { - changeAsset(mediaManager.fetchResult[0]) + if mediaManager.hasResultItems, + let firstAsset = mediaManager.getAsset(at: 0) { + changeAsset(firstAsset) v.collectionView.reloadData() v.collectionView.selectItem(at: IndexPath(row: 0, section: 0), - animated: false, - scrollPosition: UICollectionView.ScrollPosition()) - if !multipleSelectionEnabled && YPConfig.library.preSelectItemOnMultipleSelection { + animated: false, + scrollPosition: UICollectionView.ScrollPosition()) + if !isMultipleSelectionEnabled && YPConfig.library.preSelectItemOnMultipleSelection { addToSelection(indexPath: IndexPath(row: 0, section: 0)) } } else { @@ -277,7 +279,12 @@ internal final class YPLibraryVC: UIViewController, YPPermissionCheckable { } } - func changeAsset(_ asset: PHAsset) { + func changeAsset(_ asset: PHAsset?) { + guard let asset = asset else { + print("No asset to change.") + return + } + delegate?.libraryViewStartedLoadingImage() let completion = { (isLowResIntermediaryImage: Bool) in @@ -361,7 +368,7 @@ internal final class YPLibraryVC: UIViewController, YPPermissionCheckable { } internal func fetchStoredCrop() -> YPLibrarySelection? { - if self.multipleSelectionEnabled, + if self.isMultipleSelectionEnabled, self.selectedItems.contains(where: { $0.index == self.currentlySelectedIndex }) { guard let selectedAssetIndex = self.selectedItems .firstIndex(where: { $0.index == self.currentlySelectedIndex }) else { @@ -441,7 +448,7 @@ internal final class YPLibraryVC: UIViewController, YPPermissionCheckable { } // Multiple selection - if self.multipleSelectionEnabled && self.selectedItems.count > 1 { + if self.isMultipleSelectionEnabled && self.selectedItems.count > 1 { // Check video length for asset in selectedAssets { diff --git a/YPImagePicker.xcodeproj/project.pbxproj b/YPImagePicker.xcodeproj/project.pbxproj index 4188dfc88..b7d10b279 100644 --- a/YPImagePicker.xcodeproj/project.pbxproj +++ b/YPImagePicker.xcodeproj/project.pbxproj @@ -189,6 +189,8 @@ EBD2B641207B7D7400E711C2 /* YPMediaItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD2B640207B7D7400E711C2 /* YPMediaItem.swift */; }; EBD66105208104EA00EA276E /* YPIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD66104208104EA00EA276E /* YPIcons.swift */; }; EBD66107208104F400EA276E /* YPColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD66106208104F400EA276E /* YPColors.swift */; }; + EBDDF0EB278784A900A372FD /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBDDF0EA278784A900A372FD /* Array+Extensions.swift */; }; + EBDDF0EC278784A900A372FD /* Array+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBDDF0EA278784A900A372FD /* Array+Extensions.swift */; }; EBE6CA48210389ED005B0A6A /* CIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBE6CA47210389ED005B0A6A /* CIImage+Extensions.swift */; }; /* End PBXBuildFile section */ @@ -334,6 +336,7 @@ EBD66104208104EA00EA276E /* YPIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YPIcons.swift; sourceTree = ""; }; EBD66106208104F400EA276E /* YPColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YPColors.swift; sourceTree = ""; }; EBDDF0E62787069E00A372FD /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; + EBDDF0EA278784A900A372FD /* Array+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extensions.swift"; sourceTree = ""; }; EBE6CA47210389ED005B0A6A /* CIImage+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CIImage+Extensions.swift"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -553,6 +556,7 @@ 321E6D86240CFABC00D76CD8 /* UINavigationBar+Extensions.swift */, EB473E16208E192800D16105 /* URL+Extensions.swift */, EBA37BC826F757FF005DAAD4 /* Bundle+Extensions.swift */, + EBDDF0EA278784A900A372FD /* Array+Extensions.swift */, ); path = Extensions; sourceTree = ""; @@ -937,6 +941,7 @@ 99C6D6C91F1FB5C100711DB2 /* YPBottomPager.swift in Sources */, EBA37BBC26F74CE0005DAAD4 /* YPPhotoFiltersVC.swift in Sources */, 99CF6D2B201CB96700487F77 /* YPVideoCaptureHelper.swift in Sources */, + EBDDF0EC278784A900A372FD /* Array+Extensions.swift in Sources */, EBA37AFA26F7300C005DAAD4 /* YPPermissionManager.swift in Sources */, 99019E4E2018CD31007325C2 /* YPPagerMenu.swift in Sources */, 99C6D6C01F1FB5C100711DB2 /* YPAssetZoomableView.swift in Sources */, @@ -983,6 +988,7 @@ EBA37B9826F74CBA005DAAD4 /* YPVideoProcessor.swift in Sources */, EBA37B8526F74CBA005DAAD4 /* YPLibraryVC.swift in Sources */, EBA37B9E26F74CBA005DAAD4 /* YPDragDirection.swift in Sources */, + EBDDF0EB278784A900A372FD /* Array+Extensions.swift in Sources */, EBA37B5226F749C6005DAAD4 /* AppDelegate.swift in Sources */, EBA37BB126F74CBA005DAAD4 /* YPLoaders.swift in Sources */, EBA37B7526F74CBA005DAAD4 /* YPLibraryVC+PanGesture.swift in Sources */,