How to use PHCachingImageManager

Alexander Scholz picture Alexander Scholz · May 29, 2015 · Viewed 8.1k times · Source

I know that there is already this question on SO, but I don't think that the given answer is satisfying/complete: How can IOS Photos app can show hundreds of photos in one screen?

What I want to achieve

I want something like the image selection in whatsapp (iOS) (see screenshot). When you open the camera, there is also a horizontal slider where you can see all images from your gallery.

whatsapp screenshot

What I tried

Right now I have the following code in my appDelegate:

let options = PHFetchOptions()

options.sortDescriptors = [
    NSSortDescriptor(key: "creationDate", ascending: true)
]
if let results = PHAsset.fetchAssetsWithMediaType(.Image, options: options) {

    results.enumerateObjectsUsingBlock { (object, idx, _) in
        if let asset = object as? PHAsset {
            Variables.assets.append(asset)
        }
    }

    println(Variables.assets.count)

    Variables.imageManager.startCachingImagesForAssets(Variables.assets, targetSize: CGSizeMake(viewWidth, viewWidth), contentMode: .AspectFill, options: self.requestOptions)
}

Later I load the images in a UITableViewController and call the following function on scroll:

func fetchPhotoAtIndex(index : Int) {

    let asset = Variables.assets[Variables.assets.count - 1 - index] as PHAsset

    Variables.imageManager.requestImageForAsset(asset, targetSize: CGSizeMake(self.viewWidth, self.viewWidth), contentMode: .AspectFill, options: self.requestOptions, resultHandler: { (image, _) in

        println("\(asset.localIdentifier) \(asset.creationDate) ")

        [...]

        self.tableView.reloadData()
    })

}

This works well but I have the problem that on every app start all my assets get cached. Is this the right approach for my problem? How can I display the gallery photos in realtime like whatsapp does?

Answer

jarora picture jarora · Jul 4, 2015

Refer this Photos Framework example In this project you should look into the file AAPLAssetGridViewController.m and how it handles caching based on your scroll position.

 - (void)updateCachedAssets
        {
            BOOL isViewVisible = [self isViewLoaded] && [[self view] window] != nil;
            if (!isViewVisible) { return; }
            
            // The preheat window is twice the height of the visible rect
            CGRect preheatRect = self.collectionView.bounds;
            preheatRect = CGRectInset(preheatRect, 0.0f, -0.5f * CGRectGetHeight(preheatRect));
            
            // If scrolled by a "reasonable" amount...
            CGFloat delta = ABS(CGRectGetMidY(preheatRect) - CGRectGetMidY(self.previousPreheatRect));
            if (delta > CGRectGetHeight(self.collectionView.bounds) / 3.0f) {
                
                // Compute the assets to start caching and to stop caching.
                NSMutableArray *addedIndexPaths = [NSMutableArray array];
                NSMutableArray *removedIndexPaths = [NSMutableArray array];
                
                [self computeDifferenceBetweenRect:self.previousPreheatRect andRect:preheatRect removedHandler:^(CGRect removedRect) {
                    NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:removedRect];
                    [removedIndexPaths addObjectsFromArray:indexPaths];
                } addedHandler:^(CGRect addedRect) {
                    NSArray *indexPaths = [self.collectionView aapl_indexPathsForElementsInRect:addedRect];
                    [addedIndexPaths addObjectsFromArray:indexPaths];
                }];
                
                NSArray *assetsToStartCaching = [self assetsAtIndexPaths:addedIndexPaths];
                NSArray *assetsToStopCaching = [self assetsAtIndexPaths:removedIndexPaths];
                
                [self.imageManager startCachingImagesForAssets:assetsToStartCaching
                                                    targetSize:AssetGridThumbnailSize
                                                   contentMode:PHImageContentModeAspectFill
                                                       options:nil];
                [self.imageManager stopCachingImagesForAssets:assetsToStopCaching
                                                   targetSize:AssetGridThumbnailSize
                                                  contentMode:PHImageContentModeAspectFill
                                                      options:nil];
        
                 self.previousPreheatRect = preheatRect;
            }
        }
        //In your case this computeDifference method changes to handle horizontal scrolling
            - (void)computeDifferenceBetweenRect:(CGRect)oldRect andRect:(CGRect)newRect removedHandler:(void (^)(CGRect removedRect))removedHandler addedHandler:(void (^)(CGRect addedRect))addedHandler
            {
                if (CGRectIntersectsRect(newRect, oldRect)) {
                    CGFloat oldMaxY = CGRectGetMaxY(oldRect);
                    CGFloat oldMinY = CGRectGetMinY(oldRect);
                    CGFloat newMaxY = CGRectGetMaxY(newRect);
                    CGFloat newMinY = CGRectGetMinY(newRect);
                    if (newMaxY > oldMaxY) {
                        CGRect rectToAdd = CGRectMake(newRect.origin.x, oldMaxY, newRect.size.width, (newMaxY - oldMaxY));
                        addedHandler(rectToAdd);
                    }
                    if (oldMinY > newMinY) {
                        CGRect rectToAdd = CGRectMake(newRect.origin.x, newMinY, newRect.size.width, (oldMinY - newMinY));
                        addedHandler(rectToAdd);
                    }
                    if (newMaxY < oldMaxY) {
                        CGRect rectToRemove = CGRectMake(newRect.origin.x, newMaxY, newRect.size.width, (oldMaxY - newMaxY));
                        removedHandler(rectToRemove);
                    }
                    if (oldMinY < newMinY) {
                        CGRect rectToRemove = CGRectMake(newRect.origin.x, oldMinY, newRect.size.width, (newMinY - oldMinY));
                        removedHandler(rectToRemove);
                    }
                } else {
                    addedHandler(newRect);
                    removedHandler(oldRect);
                }
            }
            
        

- (NSArray *)assetsAtIndexPaths:(NSArray *)indexPaths
    {
        if (indexPaths.count == 0) { return nil; }
        
        NSMutableArray *assets = [NSMutableArray arrayWithCapacity:indexPaths.count];
        for (NSIndexPath *indexPath in indexPaths) {
            PHAsset *asset = self.assetsFetchResults[indexPath.item];
            [assets addObject:asset];
        }
        return assets;
    }

The image caching manager heats up with your assets of the desired size and then you can retrieve them from the image caching manager itself.

Hope it helps you.