6

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?

Community
  • 1
  • 1
Alexander Scholz
  • 2,100
  • 1
  • 20
  • 35
  • Why do you want to the assets when you can fetch them again? Storing them can be tricky as some assets might have been deleted/added/modified after your app was killed. Also you do not have to fetch all of them again. You should just fetch the ones you need to show the user on the screen at that time. Some context on the need behind storing them would help. – jarora Jun 06 '15 at 15:13
  • @jarora I edited my whole question, provided code and a example I want to achieve. – Alexander Scholz Jun 15 '15 at 07:47

1 Answers1

4

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.

Community
  • 1
  • 1
jarora
  • 5,384
  • 2
  • 34
  • 46
  • Hi, I'm using your code in my app and the line `PHAsset *asset = self.assetsFetchResults[indexPath.item];` throws the following exception for me: `*** Terminating app due to uncaught exception 'NSRangeException', reason: '0x1957a880: index (0) beyond bounds (0)'` This does not happen every time, i.e. I know about this exception from HockeyApp. Any idea what could be causing this? – Banana Sep 23 '15 at 12:34
  • Well i have been getting similar exceptions as well but i was not able to nail the reason behind it. The only workaround i know is adding the check: indexPath.item < self.assetsFetchResults.count before adding the asset to 'assets' array – jarora Sep 23 '15 at 13:55