// // AssetCollectionViewController.swift // Tusker // // Created by Shadowfacts on 1/1/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import UIKit import Photos private let reuseIdentifier = "assetCell" private let cameraReuseIdentifier = "showCameraCell" protocol AssetCollectionViewControllerDelegate: class { func shouldSelectAsset(_ asset: PHAsset) -> Bool func didSelectAssets(_ assets: [PHAsset]) func captureFromCamera() } class AssetCollectionViewController: UICollectionViewController { weak var delegate: AssetCollectionViewControllerDelegate? var flowLayout: UICollectionViewFlowLayout { return collectionViewLayout as! UICollectionViewFlowLayout } var availableWidth: CGFloat! var thumbnailSize: CGSize! let imageManager = PHCachingImageManager() var fetchResult: PHFetchResult! var selectedAssets: [PHAsset] { return collectionView.indexPathsForSelectedItems?.map({ (indexPath) in fetchResult.object(at: indexPath.row - 1) }) ?? [] } init() { super.init(collectionViewLayout: UICollectionViewFlowLayout()) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(donePressed)) collectionView.alwaysBounceVertical = true collectionView.register(UINib(nibName: "AssetCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: reuseIdentifier) collectionView.register(UINib(nibName: "ShowCameraCollectionViewCell", bundle: .main), forCellWithReuseIdentifier: cameraReuseIdentifier) let options = PHFetchOptions() options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] fetchResult = fetchAssets(with: options) collectionView.allowsMultipleSelection = true setEditing(true, animated: false) updateItemsSelected() if let singleFingerPanGesture = collectionView.gestureRecognizers?.first(where: { $0.name == "multi-select.singleFingerPanGesture" }), let interactivePopGesture = navigationController?.interactivePopGestureRecognizer { singleFingerPanGesture.require(toFail: interactivePopGesture) } } 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 } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) let scale = UIScreen.main.scale let cellSize = flowLayout.itemSize thumbnailSize = CGSize(width: cellSize.width * scale, height: cellSize.height * scale) } open func fetchAssets(with options: PHFetchOptions) -> PHFetchResult { return PHAsset.fetchAssets(with: options) } func updateItemsSelected() { let selected = collectionView.indexPathsForSelectedItems?.count ?? 0 navigationItem.title = "\(selected) selected" } // MARK: UICollectionViewDataSource override func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return fetchResult.count + 1 } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { if indexPath.row == 0 { return collectionView.dequeueReusableCell(withReuseIdentifier: cameraReuseIdentifier, for: indexPath) } else { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! AssetCollectionViewCell let asset = fetchResult.object(at: indexPath.row - 1) cell.updateUI(asset: asset) imageManager.requestImage(for: asset, targetSize: thumbnailSize, contentMode: .aspectFill, options: nil) { (image, _) in guard let image = image else { return } DispatchQueue.main.async { guard cell.assetIdentifier == asset.localIdentifier else { return } cell.thumbnailImage = image } } return cell } } // MARK: UICollectionViewDelegate override func collectionView(_ collectionView: UICollectionView, shouldBeginMultipleSelectionInteractionAt indexPath: IndexPath) -> Bool { return true } override func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { if indexPath.row > 0, let delegate = delegate { let asset = fetchResult.object(at: indexPath.row - 1) return delegate.shouldSelectAsset(asset) } return true } override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if indexPath.row == 0 { collectionView.deselectItem(at: indexPath, animated: false) delegate?.captureFromCamera() } else { updateItemsSelected() } } override func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { updateItemsSelected() } override func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { if indexPath.row == 0 { return nil } else { let asset = fetchResult.object(at: indexPath.row - 1) return UIContextMenuConfiguration(identifier: nil, previewProvider: { () -> UIViewController? in return AssetPreviewViewController(asset: asset) }, actionProvider: nil) } } // MARK: - Interaction @objc func donePressed() { delegate?.didSelectAssets(selectedAssets) dismiss(animated: true) } }