我正在尝试将用户本地相册中的照片加载到UICollectionView
。当应用程序启动时,需要很长时间才能加载collectionView。我试过索引项目,发现最慢的部分源于照片的拉动位置。以下是索引
来自索引的标记代码
let imageView = cell.imgView
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .fastFormat
let scale = min(2.0, UIScreen.main.scale)
let requestImageSize = CGSize(width: cell.bounds.width * scale, height: cell.bounds.height * scale)
PHImageManager.default().requestImage(for: fetchResult.object(at: indexPath.item) , targetSize: requestImageSize, contentMode: .aspectFill, options: requestOptions, resultHandler: {
image,error in
if let error = error{
print(error)
}
self.imageArray.append(image!)
imageView.image = image!
imageView.clipsToBounds = false
imageView.contentMode = .scaleAspectFill
})
整个项目代码
import UIKit
import Photos
class ViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout,UICollectionViewDataSource,GreedoCollectionViewLayoutDataSource {
var imageArray:[UIImage] = [UIImage]()
var collectionViewSizeCalculator:GreedoCollectionViewLayout?
var collectionView:UICollectionView! = {
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 1
layout.minimumLineSpacing = 2.0
layout.scrollDirection = .vertical
let CV = UICollectionView(frame: UIScreen.main.bounds, collectionViewLayout: layout)
CV.translatesAutoresizingMaskIntoConstraints = false
return CV
}()
var fetchResult: PHFetchResult<PHAsset> = PHFetchResult()
override func viewDidLoad() {
super.viewDidLoad()
self.collectionViewSizeCalculator = GreedoCollectionViewLayout(collectionView: self.collectionView)
self.collectionViewSizeCalculator?.dataSource = self
self.collectionViewSizeCalculator?.rowMaximumHeight = self.collectionView.bounds.height / 3;
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 5.0
layout.minimumLineSpacing = 5.0
layout.sectionInset = UIEdgeInsetsMake(10.0, 5.0, 5.0, 5.0)
self.collectionView.collectionViewLayout = layout
//End Of new Greedo Stuff in ViewDidLoad()
self.collectionView.delegate = self
self.collectionView.dataSource = self
collectionView.register(customPhotoCell.self, forCellWithReuseIdentifier: "Cell")
self.collectionView.backgroundColor = .white
self.view.addSubview(collectionView)
setUpLayout()
fetchAssets()
let photos = PHPhotoLibrary.authorizationStatus()
if photos == .notDetermined {
PHPhotoLibrary.requestAuthorization({status in
if status == .authorized{
print("permission granted")
DispatchQueue.main.async {
self.fetchAssets()
self.collectionView.reloadData()
}
} else {
print("permission not granted")
}
})
}
}
func setUpLayout(){
collectionView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true
collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true
collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true
collectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true
}
func fetchAssets(){
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
fetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! customPhotoCell
let imageView = cell.imgView
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .highQualityFormat
let scale = min(2.0, UIScreen.main.scale)
let requestImageSize = CGSize(width: cell.bounds.width * scale, height: cell.bounds.height * scale)
PHImageManager.default().requestImage(for: fetchResult.object(at: indexPath.item) , targetSize: requestImageSize, contentMode: .aspectFill, options: requestOptions, resultHandler: {
image,error in
if let error = error{
print(error)
}
self.imageArray.append(image!)
imageView.image = image!
imageView.clipsToBounds = false
imageView.contentMode = .scaleAspectFill
})
cell.layer.masksToBounds = true
cell.layer.cornerRadius = 10
cell.backgroundColor = .white
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return fetchResult.count
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 5.0
}
//Greedo Size for itemAt
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return self.collectionViewSizeCalculator!.sizeForPhoto(at: indexPath)
}
func greedoCollectionViewLayout(_ layout: GreedoCollectionViewLayout?, originalImageSizeAt indexPath: IndexPath?) -> CGSize {
if (indexPath?.item)! < fetchResult.count{
let asset = fetchResult[(indexPath?.item)!]
return CGSize(width: asset.pixelWidth, height: asset.pixelHeight)
}
return CGSize(width: 0.1, height: 0.1)
}
//New stuff
var lastOffsetY:CGFloat = 0
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
lastOffsetY = scrollView.contentOffset.y
}
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
if(scrollView.contentOffset.y > self.lastOffsetY){
UIView.animate(withDuration: 1.0, animations: {
self.collectionView.transform = CGAffineTransform(translationX: 0, y: -50)
}, completion: nil)
}
}
}
class customPhotoCell:UICollectionViewCell{
var imgView:UIImageView = {
let imgV = UIImageView()
imgV.backgroundColor = .orange
imgV.translatesAutoresizingMaskIntoConstraints = false
return imgV
}()
override init(frame: CGRect) {
super.init(frame: frame)
print("Inside of init")
self.addSubview(imgView)
self.layer.cornerRadius = 10.0
imgView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
imgView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
imgView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0).isActive = true
imgView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension UIImageView{
func fetchImage(asset: PHAsset, contentMode: PHImageContentMode, targetSize: CGSize) {
let options = PHImageRequestOptions()
options.version = .original
PHImageManager.default().requestImage(for: asset, targetSize: targetSize, contentMode: contentMode, options: options) { image, _ in
guard let image = image else { return }
switch contentMode {
case .aspectFill:
self.contentMode = .scaleAspectFill
case .aspectFit:
self.contentMode = .scaleAspectFit
}
self.image = image
}
}
}
extension UIImage{
func resizeImageWith() -> UIImage {
let aspectRatio = CGFloat(self.size.width / self.size.height)
let newWidth = UIScreen.main.bounds.width
let newSize = CGSize(width: newWidth, height: newWidth/aspectRatio)
UIGraphicsBeginImageContextWithOptions(newSize, true, 0)
self.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: newSize))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
}
其他布局代码
protocol GreedoSizeCalculatorDataSource {
func greedoSizeCalculator(_ layout: GreedoSizeCalculator?, originalImageSizeAt indexPath: IndexPath?) -> CGSize
}
class GreedoSizeCalculator {
//private var _sizeCache = [AnyHashable: Any]()
private var sizeCache = [IndexPath:CGSize]()
private var leftOvers:[CGSize] = [CGSize]()
var lastIndexPathAdded:IndexPath?
var dataSource: GreedoSizeCalculatorDataSource?
var rowMaximumHeight: CGFloat = 0.0
var isFixedHeight = false
var contentWidth: CGFloat = 0.0
var interItemSpacing: CGFloat = 2.0
func sizeForPhoto(at indexPath:IndexPath?)->CGSize{
print("I made it rowHeight: \(rowMaximumHeight)")
if let indexPath = indexPath{
if sizeCache[indexPath] == nil{
lastIndexPathAdded = indexPath
computeSizes(at:indexPath)
}
}
var size: CGSize? = sizeCache[indexPath!]
if((size?.width)! < CGFloat(0.0) || (size?.height)! < CGFloat(0.0)){
size = CGSize.zero
}
return size!
}
func clearCache(){
self.sizeCache.removeAll()
}
func clearCache(after indexPath:IndexPath?){
sizeCache.removeValue(forKey: indexPath!)
for existingIndexPath in sizeCache.keys{
if indexPath?.compare(existingIndexPath) == .orderedDescending{
sizeCache.removeValue(forKey: existingIndexPath)
}
}
}
func computeSizes(at indexPath:IndexPath?){
var photoSize = dataSource?.greedoSizeCalculator(self, originalImageSizeAt: indexPath)
if (photoSize?.width)! < CGFloat(1) || (photoSize?.height)! < CGFloat(1) {
// Photo with no height or width
print("Photo has no height or width")
photoSize?.width = rowMaximumHeight
photoSize?.height = rowMaximumHeight
}
print("The photo size at index: \((indexPath?.item)!) is \nwidth: \((photoSize?.width)!), height: \((photoSize?.height)!)")
leftOvers.append(photoSize!)
print("Index:\(indexPath?.item) leftOvers.count: \(leftOvers.count)")
var enoughContentForTheRow = false
var rowHeight:CGFloat = rowMaximumHeight
var widthMultiplier:CGFloat = 1.0
//Calculations For Variable HeightGrid
var totalAspectRatio:CGFloat = 0.0
for leftOver in leftOvers{
print("current aspect ratio: \(leftOver.width/leftOver.height)")
totalAspectRatio += leftOver.width/leftOver.height
}
print("totalAspectRatio: \(totalAspectRatio)")
//Aspect Ratio Calculation how to find the height of the row!!!!!!!!!!!!!!!!!!!!!!!
rowHeight = CGFloat(contentWidth)/CGFloat(totalAspectRatio)
print("rowHeight: \(rowHeight)")
enoughContentForTheRow = rowHeight < rowMaximumHeight
if enoughContentForTheRow {
var availableSpace: CGFloat = contentWidth
print("Available space: \(availableSpace)")
var index: Int = 0
for leftOver in leftOvers{
var newWidth:CGFloat = floor((rowHeight*leftOver.width)/leftOver.height)
print("new width: \(newWidth)")
newWidth = min(availableSpace, newWidth)
//add the size into the cahce
self.sizeCache[lastIndexPathAdded!] = CGSize(width: newWidth, height: rowHeight)
availableSpace -= newWidth
availableSpace -= self.interItemSpacing
// We need to keep track of the last index path added
self.lastIndexPathAdded = IndexPath(item:(lastIndexPathAdded?.item)!+1,section:(lastIndexPathAdded?.section)!)
index += 1
}
leftOvers.removeAll()
}
else{
// The line is not full, let's ask the next photo and try to fill up the line
computeSizes(at: IndexPath(item:(indexPath?.item)!+1,section:(indexPath?.section)!))
}
}
}
protocol GreedoCollectionViewLayoutDataSource {
func greedoCollectionViewLayout(_ layout: GreedoCollectionViewLayout?, originalImageSizeAt indexPath: IndexPath?) -> CGSize
}
class GreedoCollectionViewLayout:GreedoSizeCalculatorDataSource {
var dataSource: GreedoCollectionViewLayoutDataSource?
var rowMaximumHeight: CGFloat = 0.0
var isFixedHeight = false
var collectionView:UICollectionView?
private var _greedo: GreedoSizeCalculator?
var greedo: GreedoSizeCalculator? {
if _greedo == nil {
_greedo = GreedoSizeCalculator()
_greedo?.rowMaximumHeight = 200
_greedo?.isFixedHeight = false
_greedo?.dataSource = self
}
return _greedo
}
init(collectionView: UICollectionView?) {
self.collectionView = collectionView
}
func sizeForPhoto(at indexPath: IndexPath?) -> CGSize {
var contentWidth:CGFloat = (self.collectionView?.bounds.size.width)!
var interitemspacing:CGFloat = 2.0
let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout
if layout != nil {
contentWidth -= (layout?.sectionInset.left ?? 0.0) + (layout?.sectionInset.right ?? 0.0)
interitemspacing = (layout?.minimumInteritemSpacing)!
layout?.minimumLineSpacing = 5.0
layout?.minimumInteritemSpacing = 5.0
}
greedo?.contentWidth = contentWidth
greedo?.interItemSpacing = interitemspacing
return (greedo?.sizeForPhoto(at: indexPath))!
}
func clearCache() {
greedo?.clearCache()
}
func clearCache(after indexPath: IndexPath?) {
greedo?.clearCache(after: indexPath)
}
func rowmaximumHeight() -> CGFloat {
return (greedo?.rowMaximumHeight)!
}
func setRowMaximumHeight(_ rowMaximumHeight: CGFloat) {
greedo?.rowMaximumHeight = rowMaximumHeight
}
func greedoSizeCalculator(_ layout: GreedoSizeCalculator?, originalImageSizeAt indexPath: IndexPath?) -> CGSize {
return dataSource!.greedoCollectionViewLayout(self, originalImageSizeAt: indexPath)
}
}