John Doe
John Doe

Reputation: 1609

Retrieve Multiple Images Using PHAsset

I'm trying to retrieve over 1,000 images from the user's camera roll with PHAsset but it ends up crashing or taking a long time if it's just thumbnails. Here is my function where I retrieve the images...

func retrieveImages(thumbnail: Bool) {

    /* Retrieve the items in order of modification date, ascending */
    let options = PHFetchOptions()
    options.sortDescriptors = [NSSortDescriptor(key: "modificationDate",
        ascending: false)]

    /* Then get an object of type PHFetchResult that will contain
    all our image assets */
    let assetResults = PHAsset.fetchAssetsWithMediaType(.Image,
        options: options)

    let imageManager = PHCachingImageManager()

    assetResults.enumerateObjectsUsingBlock{(object: AnyObject!,
        count: Int,
        stop: UnsafeMutablePointer<ObjCBool>) in


        if object is PHAsset{
            let asset = object as! PHAsset
            print("Inside  If object is PHAsset, This is number 1")

            var imageSize: CGSize!

            if thumbnail == true {
                imageSize = CGSize(width: 100, height: 100)

            } else {
                imageSize = CGSize(width: self.cameraView.bounds.width, height: self.cameraView.bounds.height)

            }
            /* For faster performance, and maybe degraded image */
            let options = PHImageRequestOptions()
            options.deliveryMode = .FastFormat
            options.synchronous = true

            imageManager.requestImageForAsset(asset,
                targetSize: imageSize,
                contentMode: .AspectFill,
                options: options,
                resultHandler: { (image, _: [NSObject : AnyObject]?) -> Void in
                    if thumbnail == true {
                        self.libraryImageThumbnails.append(image!)
                        self.collectionTable.reloadData()
                    } else {
                        self.libraryImages.append(image!)
                        self.collectionTable.reloadData()

                    }
            })

            /* The image is now available to us */

            print("enum for image, This is number 2")



            print("Inside  If object is PHAsset, This is number 3")
        }
        print("Outside If object is PHAsset, This is number 4")

    }



}

Please tell me if any more information is needed. Thank you!

Upvotes: 1

Views: 2294

Answers (2)

rickster
rickster

Reputation: 126177

Generally you don't want to be loading tons of full-size or screen-sized images and keeping them in memory yourself. For that size, you're only going to be presenting one (or two or three, if you want to preload for screen-transition animations) at a time, so you don't need to fetch more than that at once.

Similarly, loading hundreds or thousands of thumbnails and caching them yourself will cause you memory issues. (Telling your collection view to reloadData over and over again causes a lot of churn, too.) Instead, let the Photos framework manage them — it has features for making sure that thumbnails are generated only when needed and cached between uses, and even for helping you with tasks like grabbing only the thumbnails you need as the user scrolls in a collection view.

Apple's Using Photos Framework sample code project shows several best practices for fetching images. Here's a summary of some of the major points:

  1. AssetGridViewController, which manages a collection view of thumbnails, caches a PHFetchResult<PHAsset> that's given to it upon initialization. Info about the fetch result (like its count) is all that's needed for the basic management of the collection view.

  2. AssetGridViewController requests thumbnail images from PHImageManager individually, only when the collection view asks for a cell to display. This means that we only create a UIImage when we know a cell is going to be seen, and we don't have to tell the collection view to reload everything over and over again.

On its own, step 2 would lead to slow scrolling, or rather, to slow catch-up of images loading into cells after you scroll. So, we also do:

  1. Instead of using PHImageManager.default(), we keep an instance of PHCachingImageManager, and tell it what set of images we want to "preheat" the cache for as the user scrolls.

    AssetGridViewController has some extra logic for keeping track of the visible rect of the scroll view so that the image manager knows to prepare thumbnails for the currently visible items (plus a little bit of scroll-ahead), and can free resources for items no longer visible if it needs to. Note that prefetching doesn't create UIImages, just starts the necessary loading from disk or downloading from iCloud, so your memory usage stays small.

  2. On selecting an item in the collection view (to segue to a full-screen presentation of that photo), AssetViewController uses the default PHImageManager to request a screen-sized (not full-sized) image.

    Opportunistic delivery means that we'll get a lower-quality version (basically the same thumbnail that Photos has already cached for us from its previous use in the collection view) right away, and a higher quality version soon after (asynchronously, after which we automatically update the view).

    If you need to support zooming (that sample code app doesn't, but it's likely a real app would), you could either request a full-size image right away after going to the full-screen view (at the cost of taking longer to transition from blown-up-thumbnail to fullscreen image), or start out by requesting a screen-sized image and then also requesting a full-sized image for later delivery.

Upvotes: 1

AllHailMegatron
AllHailMegatron

Reputation: 13

In CellForRowAtIndexPath use the PHImageManager's RequestImageForAsset method to lazy load the image property on your UIImageView.

Upvotes: 0

Related Questions