From 6138fc7748e0564b09b219e5c0c55e1c44ece6db Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Fri, 21 Jan 2022 12:10:29 -0500 Subject: [PATCH] Add select more photos option to asset picker --- Tusker.xcodeproj/project.pbxproj | 8 +- Tusker/Info.plist | 2 +- .../AssetCollectionViewController.swift | 95 ++++++++++++------- ...AssetPickerControlCollectionViewCell.swift | 47 +++++++++ .../ShowCameraCollectionViewCell.xib | 37 -------- 5 files changed, 111 insertions(+), 78 deletions(-) create mode 100644 Tusker/Views/Asset Picker/AssetPickerControlCollectionViewCell.swift delete mode 100644 Tusker/Views/Asset Picker/ShowCameraCollectionViewCell.xib diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 0a7a7273..34943712 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -90,7 +90,6 @@ D623A5412635FB3C0095BD04 /* PollOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A5402635FB3C0095BD04 /* PollOptionView.swift */; }; D623A543263634100095BD04 /* PollOptionCheckboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D623A542263634100095BD04 /* PollOptionCheckboxView.swift */; }; D625E4822588262A0074BB2B /* DraggableTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */; }; - D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */; }; D626493523BD94CE00612E6E /* CompositionAttachmentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */; }; D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */; }; D626493923C0FD0000612E6E /* AllPhotosTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */; }; @@ -203,6 +202,7 @@ D681E4D9246E346E0053414F /* AccountActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D681E4D8246E346E0053414F /* AccountActivityItemSource.swift */; }; D68232F72464F4FD00325FB8 /* ComposeDrawingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */; }; D686BBE324FBF8110068E6AA /* WrappedProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */; }; + D68ACE5D279B1ABA001CE8EB /* AssetPickerControlCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68ACE5C279B1ABA001CE8EB /* AssetPickerControlCollectionViewCell.swift */; }; D68C2AE325869BAB00548EFF /* AuxiliarySceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68C2AE225869BAB00548EFF /* AuxiliarySceneDelegate.swift */; }; D68E525B24A3D77E0054355A /* TuskerRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E525A24A3D77E0054355A /* TuskerRootViewController.swift */; }; D68E525D24A3E8F00054355A /* SearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68E525C24A3E8F00054355A /* SearchViewController.swift */; }; @@ -495,7 +495,6 @@ D623A5402635FB3C0095BD04 /* PollOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionView.swift; sourceTree = ""; }; D623A542263634100095BD04 /* PollOptionCheckboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionCheckboxView.swift; sourceTree = ""; }; D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableTableViewCell.swift; sourceTree = ""; }; - D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShowCameraCollectionViewCell.xib; sourceTree = ""; }; D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentData.swift; sourceTree = ""; }; D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = ""; }; D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AllPhotosTableViewCell.xib; sourceTree = ""; }; @@ -611,6 +610,7 @@ D681E4D8246E346E0053414F /* AccountActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountActivityItemSource.swift; sourceTree = ""; }; D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeDrawingViewController.swift; sourceTree = ""; }; D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedProgressView.swift; sourceTree = ""; }; + D68ACE5C279B1ABA001CE8EB /* AssetPickerControlCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPickerControlCollectionViewCell.swift; sourceTree = ""; }; D68C2AE225869BAB00548EFF /* AuxiliarySceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuxiliarySceneDelegate.swift; sourceTree = ""; }; D68E525A24A3D77E0054355A /* TuskerRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerRootViewController.swift; sourceTree = ""; }; D68E525C24A3E8F00054355A /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = ""; }; @@ -974,11 +974,11 @@ children = ( D6B053A923BD2F1400A066FA /* AssetCollectionViewCell.swift */, D6B053AA23BD2F1400A066FA /* AssetCollectionViewCell.xib */, - D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */, D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */, D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */, D626493A23C1000300612E6E /* AlbumTableViewCell.swift */, D626493B23C1000300612E6E /* AlbumTableViewCell.xib */, + D68ACE5C279B1ABA001CE8EB /* AssetPickerControlCollectionViewCell.swift */, ); path = "Asset Picker"; sourceTree = ""; @@ -1923,7 +1923,6 @@ D6289E84217B795D0003D1D7 /* LargeImageViewController.xib in Resources */, D6093FB125BE0B01004811E6 /* TrendingHashtagTableViewCell.xib in Resources */, D627FF79217E950100CC0648 /* DraftsTableViewController.xib in Resources */, - D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */, D626493D23C1000300612E6E /* AlbumTableViewCell.xib in Resources */, D627944723A6AC9300D38C68 /* BasicTableViewCell.xib in Resources */, D64BC19023C18B9D000D0238 /* FollowRequestNotificationTableViewCell.xib in Resources */, @@ -2096,6 +2095,7 @@ D627944F23A9C99800D38C68 /* EditListAccountsViewController.swift in Sources */, D6945C3423AC6431005C403C /* AddSavedHashtagViewController.swift in Sources */, D627943723A552C200D38C68 /* BookmarkStatusActivity.swift in Sources */, + D68ACE5D279B1ABA001CE8EB /* AssetPickerControlCollectionViewCell.swift in Sources */, D62D2426217ABF63005076CC /* UserActivityType.swift in Sources */, D6AC956723C4347E008C9946 /* MainSceneDelegate.swift in Sources */, D6311C5025B3765B00B27539 /* ImageDataCache.swift in Sources */, diff --git a/Tusker/Info.plist b/Tusker/Info.plist index f7780369..cda7e021 100644 --- a/Tusker/Info.plist +++ b/Tusker/Info.plist @@ -56,7 +56,7 @@ NSMicrophoneUsageDescription Post videos from the camera. NSPhotoLibraryAddUsageDescription - Save photos directly from other people's posts. + Save photos directly from other people's posts. NSPhotoLibraryUsageDescription Post photos from the photo library. NSUserActivityTypes diff --git a/Tusker/Screens/Asset Picker/AssetCollectionViewController.swift b/Tusker/Screens/Asset Picker/AssetCollectionViewController.swift index 29e10581..ea12ceaf 100644 --- a/Tusker/Screens/Asset Picker/AssetCollectionViewController.swift +++ b/Tusker/Screens/Asset Picker/AssetCollectionViewController.swift @@ -18,16 +18,13 @@ protocol AssetCollectionViewControllerDelegate: AnyObject { func captureFromCamera() } -class AssetCollectionViewController: UICollectionViewController { +class AssetCollectionViewController: UIViewController, UICollectionViewDelegate { weak var delegate: AssetCollectionViewControllerDelegate? + private var collectionView: UICollectionView! private var dataSource: UICollectionViewDiffableDataSource! - private var flowLayout: UICollectionViewFlowLayout { - return collectionViewLayout as! UICollectionViewFlowLayout - } - private var availableWidth: CGFloat! private var thumbnailSize: CGSize! private let imageManager = PHCachingImageManager() @@ -41,7 +38,7 @@ class AssetCollectionViewController: UICollectionViewController { } init() { - super.init(collectionViewLayout: UICollectionViewFlowLayout()) + super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { @@ -51,6 +48,17 @@ class AssetCollectionViewController: UICollectionViewController { override func viewDidLoad() { super.viewDidLoad() + let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1/3), heightDimension: .fractionalWidth(1/3)) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalWidth(1/3)) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 3) + group.interItemSpacing = .fixed(4) + let section = NSCollectionLayoutSection(group: group) + let layout = UICollectionViewCompositionalLayout(section: section) + collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) + collectionView.delegate = self + view.addSubview(collectionView) + // use the safe area layout guide instead of letting it automatically use the safe area insets // because otherwise, when presented in a popover with the arrow on the left or right side, // the collection view content will be cut off by the width of the arrow because the popover @@ -73,16 +81,24 @@ class AssetCollectionViewController: UICollectionViewController { collectionView.allowsSelection = true collectionView.register(UINib(nibName: "AssetCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: reuseIdentifier) - collectionView.register(UINib(nibName: "ShowCameraCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: cameraReuseIdentifier) - let scale = UIScreen.main.scale - let cellSize = flowLayout.itemSize - thumbnailSize = CGSize(width: cellSize.width * scale, height: cellSize.height * scale) + let controlCell = UICollectionView.CellRegistration { cell, indexPath, itemIdentifier in + switch itemIdentifier { + case .showCamera: + cell.imageView.image = UIImage(systemName: "camera") + cell.label.text = "Take a Photo" + case .changeLimitedSelection: + cell.imageView.image = UIImage(systemName: "photo.on.rectangle.angled") + cell.label.text = "Select More Photos" + case .asset(_): + break + } + } dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { (collectionView, indexPath, item) -> UICollectionViewCell? in switch item { - case .showCamera: - return collectionView.dequeueReusableCell(withReuseIdentifier: cameraReuseIdentifier, for: indexPath) + case .showCamera, .changeLimitedSelection: + return collectionView.dequeueConfiguredReusableCell(using: controlCell, for: indexPath, item: item) case let .asset(asset): let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! AssetCollectionViewCell @@ -107,30 +123,23 @@ class AssetCollectionViewController: UICollectionViewController { let interactivePopGesture = navigationController?.interactivePopGestureRecognizer { singleFingerPanGesture.require(toFail: interactivePopGesture) } + + PHPhotoLibrary.shared().register(self) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + let scale = UIScreen.main.scale + let cellWidth = view.bounds.width / 3 + thumbnailSize = CGSize(width: cellWidth * scale, height: cellWidth * scale) + loadAssets() } - override func viewWillLayoutSubviews() { - super.viewWillLayoutSubviews() - - let availableWidth = view.bounds.inset(by: view.safeAreaInsets).width - - if self.availableWidth != availableWidth { - self.availableWidth = availableWidth - - let size = (availableWidth - 8) / 3 - flowLayout.itemSize = CGSize(width: size, height: size) - flowLayout.minimumInteritemSpacing = 4 - flowLayout.minimumLineSpacing = 4 - } - } - private func loadAssets() { + var items = [Item.showCamera] + switch PHPhotoLibrary.authorizationStatus(for: .readWrite) { case .notDetermined: PHPhotoLibrary.requestAuthorization(for: .readWrite) { (_) in @@ -142,8 +151,11 @@ class AssetCollectionViewController: UICollectionViewController { // todo: better UI for this return - case .authorized, .limited: - // todo: show "add more" button for limited access + case .authorized: + break + + case .limited: + items.append(.changeLimitedSelection) break @unknown default: @@ -156,7 +168,6 @@ class AssetCollectionViewController: UICollectionViewController { fetchResult = fetchAssets(with: options) var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.assets]) - var items: [Item] = [.showCamera] fetchResult.enumerateObjects { (asset, _, _) in items.append(.asset(asset)) } @@ -176,11 +187,11 @@ class AssetCollectionViewController: UICollectionViewController { // MARK: UICollectionViewDelegate - override func collectionView(_ collectionView: UICollectionView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool { + func collectionView(_ collectionView: UICollectionView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool { return true } - override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { + func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { guard let item = dataSource.itemIdentifier(for: indexPath) else { return false } if let delegate = delegate, case let .asset(asset) = item { @@ -189,29 +200,32 @@ class AssetCollectionViewController: UICollectionViewController { return true } - override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let item = dataSource.itemIdentifier(for: indexPath) else { return } switch item { case .showCamera: collectionView.deselectItem(at: indexPath, animated: false) delegate?.captureFromCamera() + case .changeLimitedSelection: + // todo: change observer + PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: self) case .asset(_): updateItemsSelectedCount() } } - override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { updateItemsSelectedCount() } - override func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { + func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { guard case let .asset(asset) = dataSource.itemIdentifier(for: indexPath) else { return nil } return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: { () -> UIViewController? in return AssetPreviewViewController(asset: asset) }, actionProvider: nil) } - override func collectionView(_ collectionView: UICollectionView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { + func collectionView(_ collectionView: UICollectionView, previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { if let indexPath = (configuration.identifier as? NSIndexPath) as IndexPath?, let cell = collectionView.cellForItem(at: indexPath) as? AssetCollectionViewCell { let parameters = UIPreviewParameters() @@ -237,6 +251,15 @@ extension AssetCollectionViewController { } enum Item: Hashable { case showCamera + case changeLimitedSelection case asset(PHAsset) } } + +extension AssetCollectionViewController: PHPhotoLibraryChangeObserver { + func photoLibraryDidChange(_ changeInstance: PHChange) { + DispatchQueue.main.async { + self.loadAssets() + } + } +} diff --git a/Tusker/Views/Asset Picker/AssetPickerControlCollectionViewCell.swift b/Tusker/Views/Asset Picker/AssetPickerControlCollectionViewCell.swift new file mode 100644 index 00000000..e7f7031e --- /dev/null +++ b/Tusker/Views/Asset Picker/AssetPickerControlCollectionViewCell.swift @@ -0,0 +1,47 @@ +// +// AssetPickerControlCollectionViewCell.swift +// Tusker +// +// Created by Shadowfacts on 1/21/22. +// Copyright © 2022 Shadowfacts. All rights reserved. +// + +import UIKit + +class AssetPickerControlCollectionViewCell: UICollectionViewCell { + + let imageView = UIImageView() + let label = UILabel() + + override init(frame: CGRect) { + super.init(frame: frame) + + imageView.contentMode = .scaleAspectFit + imageView.setContentHuggingPriority(.defaultLow, for: .vertical) + + label.font = .preferredFont(forTextStyle: .caption1) + label.textAlignment = .center + + let stackView = UIStackView(arrangedSubviews: [ + imageView, + label, + ]) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.alignment = .center + addSubview(stackView) + NSLayoutConstraint.activate([ + stackView.leadingAnchor.constraint(equalTo: leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: trailingAnchor), + stackView.topAnchor.constraint(equalTo: topAnchor, constant: 8), + stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8), + + imageView.widthAnchor.constraint(equalTo: widthAnchor, constant: -32) + ]) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +} diff --git a/Tusker/Views/Asset Picker/ShowCameraCollectionViewCell.xib b/Tusker/Views/Asset Picker/ShowCameraCollectionViewCell.xib deleted file mode 100644 index e237e162..00000000 --- a/Tusker/Views/Asset Picker/ShowCameraCollectionViewCell.xib +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -