Add select more photos option to asset picker

This commit is contained in:
Shadowfacts 2022-01-21 12:10:29 -05:00
parent dc1eb3d6f0
commit 6138fc7748
5 changed files with 111 additions and 78 deletions

View File

@ -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 = "<group>"; };
D623A542263634100095BD04 /* PollOptionCheckboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionCheckboxView.swift; sourceTree = "<group>"; };
D625E4812588262A0074BB2B /* DraggableTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableTableViewCell.swift; sourceTree = "<group>"; };
D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShowCameraCollectionViewCell.xib; sourceTree = "<group>"; };
D626493423BD94CE00612E6E /* CompositionAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachmentData.swift; sourceTree = "<group>"; };
D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = "<group>"; };
D626493723C0FD0000612E6E /* AllPhotosTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AllPhotosTableViewCell.xib; sourceTree = "<group>"; };
@ -611,6 +610,7 @@
D681E4D8246E346E0053414F /* AccountActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountActivityItemSource.swift; sourceTree = "<group>"; };
D68232F62464F4FD00325FB8 /* ComposeDrawingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeDrawingViewController.swift; sourceTree = "<group>"; };
D686BBE224FBF8110068E6AA /* WrappedProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedProgressView.swift; sourceTree = "<group>"; };
D68ACE5C279B1ABA001CE8EB /* AssetPickerControlCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetPickerControlCollectionViewCell.swift; sourceTree = "<group>"; };
D68C2AE225869BAB00548EFF /* AuxiliarySceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuxiliarySceneDelegate.swift; sourceTree = "<group>"; };
D68E525A24A3D77E0054355A /* TuskerRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TuskerRootViewController.swift; sourceTree = "<group>"; };
D68E525C24A3E8F00054355A /* SearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewController.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 */,

View File

@ -56,7 +56,7 @@
<key>NSMicrophoneUsageDescription</key>
<string>Post videos from the camera.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Save photos directly from other people's posts.</string>
<string>Save photos directly from other people&apos;s posts.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Post photos from the photo library.</string>
<key>NSUserActivityTypes</key>

View File

@ -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<Section, Item>!
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<AssetPickerControlCollectionViewCell, Item> { 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<Section, Item>(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<Section, Item>()
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()
}
}
}

View File

@ -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")
}
}

View File

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX">
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="80" height="80"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="camera" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="fUY-i2-EtY">
<rect key="frame" x="16" y="16.5" width="48" height="46"/>
</imageView>
</subviews>
</view>
<constraints>
<constraint firstAttribute="bottom" secondItem="fUY-i2-EtY" secondAttribute="bottom" constant="16" id="HkO-Cn-2Na"/>
<constraint firstItem="fUY-i2-EtY" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" constant="16" id="bUq-pX-5XL"/>
<constraint firstAttribute="trailing" secondItem="fUY-i2-EtY" secondAttribute="trailing" constant="16" id="fyg-fN-FJu"/>
<constraint firstItem="fUY-i2-EtY" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" constant="16" id="oOU-Tc-Z5T"/>
</constraints>
<viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
<point key="canvasLocation" x="132" y="154"/>
</collectionViewCell>
</objects>
<resources>
<image name="camera" catalog="system" width="64" height="48"/>
</resources>
</document>