title: My custom photo album collection view loads very slow
body: <p>I am loading the users photos from the photo album on their phone into a collectionView which sizes the row height based on the aspect ratio of the photos in the row. When the program runs it seems to stall in the beginning for a few seconds while it performs all of these calculations. Is there a way to speed this up. </p>
  
  <p>Custom Layout Code</p>
  
  <pre><code>import Foundation
  import UIKit
  
  protocol GreedoSizeCalculatorDataSource {
      func greedoSizeCalculator(_ layout: GreedoSizeCalculator?, originalImageSizeAt indexPath: IndexPath?) -&gt; 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?)-&gt;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)! &lt; CGFloat(0.0) || (size?.height)! &lt; 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)! &lt; CGFloat(1) || (photoSize?.height)! &lt; 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 &lt; 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?) -&gt; 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?) -&gt; 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() -&gt; CGFloat {
          return (greedo?.rowMaximumHeight)!
      }
  
      func setRowMaximumHeight(_ rowMaximumHeight: CGFloat) {
          greedo?.rowMaximumHeight = rowMaximumHeight
      }
  
      func greedoSizeCalculator(_ layout: GreedoSizeCalculator?, originalImageSizeAt indexPath: IndexPath?) -&gt; CGSize {
          return dataSource!.greedoCollectionViewLayout(self, originalImageSizeAt: indexPath)
      }
  
  
  }
  </code></pre>
  
  <p>Main view Controller Code</p>
  
  <pre><code>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&lt;PHAsset&gt; = 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) -&gt; 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) -&gt; Int {
          return fetchResult.count
      }
  
      func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -&gt; CGFloat {
          return 5.0
      }
  
      func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -&gt; CGFloat {
          return 5.0
      }
  
  
  
      //Greedo Size for itemAt
      func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -&gt; CGSize {
          return self.collectionViewSizeCalculator!.sizeForPhoto(at: indexPath)
      }
  
      func greedoCollectionViewLayout(_ layout: GreedoCollectionViewLayout?, originalImageSizeAt indexPath: IndexPath?) -&gt; CGSize {
          if (indexPath?.item)! &lt; 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 &gt; 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() -&gt; 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!
      }
  }
  </code></pre>