Created
June 26, 2022 22:54
-
-
Save tucan9389/14362294f67af202c4004c00472bca79 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ViewController.swift | |
// PhotoAlbumExample | |
// | |
// Created by tucan9389 on 2022/06/26. | |
// | |
import UIKit | |
import Photos | |
extension Date { | |
func toString(formatString: String = "yyyy-MM-dd") -> String { | |
let dateFormatter = DateFormatter() | |
dateFormatter.dateFormat = "yyyy-MM-dd" | |
return dateFormatter.string(from: self) | |
} | |
} | |
class DummyDataSource: NSObject, UICollectionViewDataSource { | |
var sectionTitleArray: [String] = [] | |
var sectionToAssetArray: [String: [PHAsset]] = [:] | |
var originalSectionTitls: [String] = [] | |
var originalSectionToAssetArray: [String: [PHAsset]] = [:] | |
var globalNumber = 0 | |
let imageManager = PHCachingImageManager() | |
lazy var options: PHImageRequestOptions = { | |
let options = PHImageRequestOptions() | |
return options | |
}() | |
func loadAssets() { | |
let allPhotosOptions = PHFetchOptions() | |
allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] | |
allPhotosOptions.predicate = NSPredicate(format: "mediaType == %d", PHAssetMediaType.image.rawValue) | |
let fetchResult = PHAsset.fetchAssets(with: allPhotosOptions) | |
print("START") | |
for i in 0..<fetchResult.count { | |
let asset = fetchResult.object(at: i) | |
let lastDateString = originalSectionTitls.last | |
let currentDateString = asset.creationDate?.toString() ?? "N/A" | |
if lastDateString != currentDateString { | |
originalSectionTitls.append(currentDateString) | |
if !originalSectionToAssetArray.keys.contains(currentDateString) { | |
originalSectionToAssetArray[currentDateString] = [] | |
} | |
} | |
originalSectionToAssetArray[currentDateString]?.append(asset) | |
} | |
print("DONE!") | |
} | |
func numberOfSections(in collectionView: UICollectionView) -> Int { | |
return sectionTitleArray.count | |
} | |
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { | |
let sectionTitle = sectionTitleArray[section] | |
return sectionToAssetArray[sectionTitle]?.count ?? 0 | |
} | |
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { | |
let sectionTitle = sectionTitleArray[indexPath.section] | |
guard let asset = sectionToAssetArray[sectionTitle]?[indexPath.item] else { return UICollectionViewCell() } | |
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PhotoCell.reuseIdentifier, for: indexPath) as? PhotoCell else { return UICollectionViewCell() } | |
cell.representedAssetIdentifier = asset.localIdentifier | |
cell.thumbnailImage = nil | |
imageManager.requestImage(for: asset, | |
targetSize: CGSize(width: 300, height: 300), | |
contentMode: .aspectFill, | |
options: options, | |
resultHandler: { image, _ in | |
guard let uiimage = image else { return } | |
guard cell.representedAssetIdentifier == asset.localIdentifier else { return } | |
cell.thumbnailImage = uiimage | |
}) | |
return cell | |
} | |
func addAssetToLastDateIfPossible(_ collectionView: UICollectionView) { | |
guard !sectionTitleArray.isEmpty else { return } | |
let lastSection = sectionTitleArray.count-1 | |
let lastDate = sectionTitleArray[lastSection] | |
let nextIndex = sectionToAssetArray[lastDate]?.count ?? 0 | |
guard nextIndex < originalSectionToAssetArray[lastDate]?.count ?? 0 else { return } | |
guard let appeningAsset = originalSectionToAssetArray[lastDate]?[nextIndex] else { return } | |
// update collectionView | |
collectionView.performBatchUpdates { | |
sectionToAssetArray[lastDate]?.append(appeningAsset) | |
collectionView.insertItems(at: [IndexPath(row: nextIndex, section: lastSection)]) | |
} | |
} | |
func addSectionIfPossible(_ collectionView: UICollectionView) { | |
guard sectionTitleArray.count < originalSectionTitls.count else { return } | |
// update collectionView | |
collectionView.performBatchUpdates { | |
let newSectionTitle = originalSectionTitls[sectionTitleArray.count] | |
sectionTitleArray.append(newSectionTitle) | |
if let firstAsset = originalSectionToAssetArray[newSectionTitle]?.first { | |
sectionToAssetArray[newSectionTitle] = [firstAsset] | |
} else { | |
sectionToAssetArray[newSectionTitle] = [] | |
} | |
collectionView.insertSections(IndexSet(integer: sectionTitleArray.count-1)) | |
} | |
} | |
func updateLastCell(_ collectionView: UICollectionView) { | |
collectionView.visibleCells.forEach { cell in | |
guard let cell = cell as? PhotoCell else { return } | |
let indexPath = collectionView.indexPath(for: cell) | |
guard indexPath?.section == sectionTitleArray.count-1, | |
let lastTitle = sectionTitleArray.last, | |
let countOfLastSection = sectionToAssetArray[lastTitle]?.count, | |
indexPath?.item == countOfLastSection-1 else { return } | |
cell.middleText = "\(globalNumber)" | |
} | |
globalNumber += 1 | |
} | |
} | |
class ViewController: UICollectionViewController { | |
var dataSource = DummyDataSource() | |
let columnLayout = ColumnFlowLayout( | |
cellsPerRow: 4, | |
minimumInteritemSpacing: 2, | |
minimumLineSpacing: 2, | |
sectionInset: UIEdgeInsets(top: 2, left: 2, bottom: 12, right: 2) | |
) | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
collectionView.collectionViewLayout = columnLayout | |
collectionView.contentInsetAdjustmentBehavior = .always | |
dataSource.loadAssets() | |
collectionView.dataSource = dataSource | |
} | |
@IBAction func addCell(_ sender: Any) { | |
dataSource.addAssetToLastDateIfPossible(collectionView) | |
} | |
@IBAction func addSection(_ sender: Any) { | |
dataSource.addSectionIfPossible(collectionView) | |
} | |
@IBAction func updateLastCell(_ sender: Any) { | |
dataSource.updateLastCell(collectionView) | |
} | |
} | |
class PhotoCell: UICollectionViewCell { | |
static let reuseIdentifier = "PhotoCell" | |
@IBOutlet weak var imageView: UIImageView! | |
@IBOutlet weak var label: UILabel! | |
var representedAssetIdentifier: String? = nil | |
var thumbnailImage: UIImage? { | |
didSet { | |
imageView.image = thumbnailImage | |
} | |
} | |
var middleText: String? { | |
didSet { | |
label.text = middleText | |
} | |
} | |
} | |
class ColumnFlowLayout: UICollectionViewFlowLayout { | |
let cellsPerRow: Int | |
init(cellsPerRow: Int, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) { | |
self.cellsPerRow = cellsPerRow | |
super.init() | |
self.minimumInteritemSpacing = minimumInteritemSpacing | |
self.minimumLineSpacing = minimumLineSpacing | |
self.sectionInset = sectionInset | |
} | |
required init?(coder aDecoder: NSCoder) { | |
fatalError("init(coder:) has not been implemented") | |
} | |
override func prepare() { | |
super.prepare() | |
guard let collectionView = collectionView else { return } | |
let marginsAndInsets = sectionInset.left + sectionInset.right + collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1) | |
let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down) | |
itemSize = CGSize(width: itemWidth, height: itemWidth) | |
} | |
override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext { | |
let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext | |
context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size | |
return context | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment