Skip to content

Instantly share code, notes, and snippets.

@tucan9389
Created June 26, 2022 22:54
Show Gist options
  • Save tucan9389/14362294f67af202c4004c00472bca79 to your computer and use it in GitHub Desktop.
Save tucan9389/14362294f67af202c4004c00472bca79 to your computer and use it in GitHub Desktop.
//
// 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