0

I have a UICollectionViewController that implemented with PHCachingImageManager that caches all the photos in the Camera Roll.

This works correctly with normal collection view layout, even with around 2000 photos.

However if I implement UICollectionViewDelegateFlowLayout, the app crashes when trying to obtain the image size for each image asset item.

//MARK: - UICollectionViewDataSource
extension ImportPhotoViewController: UICollectionViewDataSource {

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return self.fetchedPhotoAssets?.count!
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let asset = self.fetchedPhotoAssets![indexPath.item]

        //dequeue cell
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! LibraryPhotoCell
        cell.representedAssetIdentifier = asset.localIdentifier

        cell.backgroundColor = UIColor.whiteColor()
        //Request an image for the asset from the PHCachingImageManager
        self.imageManager?.requestImageForAsset(asset as! PHAsset,
                                                targetSize: ImportPhotoViewController.AssetGridThumbnailSize!,
                                                contentMode: PHImageContentMode.AspectFit,
                                                options: nil,
                                                resultHandler: { (image, _) in
                                                    if cell.representedAssetIdentifier == asset.localIdentifier {
                                                        cell.imageView.image = image
                                                    }
        })

        return cell
    }

    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        //select photo
    }
}

//MARK: - UICollectionViewDelegateFlowLayout
private var imageManager: PHCachingImageManager?

extension ImportPhotoViewController: UICollectionViewDelegateFlowLayout {

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {

        let asset = self.fetchedPhotoAssets![indexPath.item]
        var size = ImportPhotoViewController.AssetGridThumbnailSize
        self.imageManager?.requestImageForAsset(asset as! PHAsset,
                                                targetSize: ImportPhotoViewController.AssetGridThumbnailSize!,
                                                contentMode: .AspectFill,
                                                options: nil,
                                                resultHandler: { (image, _) in
                                                    size = image?.size
                                                    size!.width += 10
                                                    size!.height += 10
        })

        return size!
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
        return sectionInsets
    }
}

Caching function triggered when UI is scrolling

private func updateCachedAssets() {

    let isViewVisible = self.isViewLoaded() && ((self.collectionView as UICollectionView).window != nil)

    if !isViewVisible { return }

    //The preheat window is twice the height of the visible rect
    var preheatRect = self.collectionView.bounds
    preheatRect = CGRectInset(preheatRect, 0, -0.5 * CGRectGetHeight(preheatRect))

    //Check if the collection view is showing an area that is significantly
    //different to the last preheat area.
    let delta = abs(CGRectGetMidY(preheatRect) - CGRectGetMidY(previousPreheatRect))

    if (delta > CGRectGetHeight(self.collectionView.bounds) / 3) {
        QL1("updateCachedAssets")

        let addedIndexPaths = [] as NSMutableArray
        let removedIndexPaths = [] as NSMutableArray

        computeDifferenceBetweenRect(previousPreheatRect, newRect: preheatRect,
                                     removedHandler: { (removedRect) in
                                        let indexPaths = self.collectionView.indexPathsForElementsInRect(removedRect)
                                        removedIndexPaths.addObjectsFromArray(indexPaths as [AnyObject])
        }, addedHandler: { (addedRect) in
            let indexPaths = self.collectionView.indexPathsForElementsInRect(addedRect)
            addedIndexPaths.addObjectsFromArray(indexPaths as [AnyObject])
        })

        let assetsToStartCaching = self.assetsAtIndexPaths(addedIndexPaths)
        let assetsToStopCaching = self.assetsAtIndexPaths(removedIndexPaths)

        //update the assets the PHCachingImageManager is caching
        imageManager?.startCachingImagesForAssets(assetsToStartCaching as! [PHAsset], targetSize: ImportPhotoViewController.AssetGridThumbnailSize!, contentMode: .AspectFit, options: nil)
        imageManager?.stopCachingImagesForAssets(assetsToStopCaching as! [PHAsset], targetSize: ImportPhotoViewController.AssetGridThumbnailSize!, contentMode: .AspectFit, options: nil)

        //store the preheat rect to compare against in the future
        self.previousPreheatRect = preheatRect
    }
}

Logging suggested that the collection view was trying to obtain the dynamic size of all photo asset at once when the view loads. Am I missing anything?

maxwell
  • 3,788
  • 6
  • 26
  • 40
sunfffd
  • 141
  • 6
  • What's the crash message? I don't think that's the best place to call an asynchronous request like that – luish Jul 25 '16 at 12:09
  • @luish I see a memory spike then the app just quits. Prior to that I did get some Received Memory Warning. You are right the requestImageForAsset() async call is taking up lots of memory trying to fetch the images. I would like to keep the photos' aspect in the collection view's thumbnail though. – sunfffd Jul 25 '16 at 12:17
  • I'd recommend some caching or another strategy to fetch that information outside that function, after that you would just access and gather what you have triggered beforehand. Doing that inside the delegate could make it be called many times as the user scrolls the CollectionView or other action that constantly change the visible cells. – luish Jul 25 '16 at 12:30
  • @luish some i have already use PHCachingImageManager and cache the thumbnails as the UI scrolls, but it seems collectionView: sizeForItemAtIndexPath are called all at once according to my datasource array size. Do you think I should create an array to store the size of each PHAsset? That could be an array storing 2000+ CGSize – sunfffd Jul 28 '16 at 23:54

0 Answers0