30

I'm having a hard figuring out how to programmatically retrieve the most recent photo in the camera roll without user intervention. To be clear, I do not want to use an Image Picker, I want the app to automatically grab the newest photo when the app opens.

I know this is possible because Ive seen a similar app do it, but I cant seem to find any info on it.

jvnbt
  • 2,465
  • 4
  • 20
  • 23

7 Answers7

47

One way is to use AssetsLibrary and use n - 1 as the index for enumeration.

ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];
[assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
                             usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
                                 if (nil != group) {
                                     // be sure to filter the group so you only get photos
                                     [group setAssetsFilter:[ALAssetsFilter allPhotos]];

                                     if (group.numberOfAssets > 0) {
                                         [group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:group.numberOfAssets - 1]
                                                                 options:0
                                                              usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
                                                                  if (nil != result) {
                                                                      ALAssetRepresentation *repr = [result defaultRepresentation];
                                                                      // this is the most recent saved photo
                                                                      UIImage *img = [UIImage imageWithCGImage:[repr fullResolutionImage]];
                                                                      // we only need the first (most recent) photo -- stop the enumeration
                                                                      *stop = YES;
                                                                  }
                                                              }];
                                     }
                                 }

                                 *stop = NO;
                             } failureBlock:^(NSError *error) {
                                 NSLog(@"error: %@", error);
                             }];
richy
  • 2,716
  • 1
  • 33
  • 42
Art Gillespie
  • 8,747
  • 1
  • 37
  • 34
  • 6
    Great solution, thanks! NB: one should also make sure group.numberOfAssets > 0 to avoid an out of bounds crash for [group enumerateAssetsAtIndexes:-1] – flatpickles Jan 31 '14 at 06:03
  • it's working in ios8.0 and above but not in ios7.0 is there any alternate solution? – kb920 Jan 21 '15 at 05:04
  • As of iOS 9.0 `-[enumerateGroupsWithTypes:usingBlock:failureBlock:]` is deprecated. Any alternatives? – Ken M. Haggerty Sep 22 '15 at 21:57
  • Ah, nevermind – the entire Assets Library framework is deprecated as of iOS 9.0 in favor of the Photos framework. – Ken M. Haggerty Sep 23 '15 at 16:25
  • Also note that `-[enumerateGroupsWithTypes:usingBlock:failureBlock:]` is an asynchronous parallel process. To stop all parallel threads once you've obtained your thumbnail, I added `__block BOOL foundThumbnail = NO` prior to calling `enumerate`, then set `foundThumbnail = YES` once I found the thumbnail. – Ken M. Haggerty Sep 23 '15 at 16:28
24

Instead of messing with the index, you can enumerate through the list in reverse. This pattern works well if you want the most recent image or if you want to list the images in a UICollectionView with the most recent image first.

Example to return the most recent image:

[group enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop) {
    if (asset) {
        ALAssetRepresentation *repr = [asset defaultRepresentation];
        UIImage *img = [UIImage imageWithCGImage:[repr fullResolutionImage]];
        *stop = YES;
    }
}];
Herman J. Radtke III
  • 1,804
  • 1
  • 24
  • 29
11

In iOS 8, Apple added the Photos library which makes for easier querying. In iOS 9, ALAssetLibrary is deprecated.

Here's some Swift code to get the most recent photo taken with that framework.

import UIKit
import Photos

struct LastPhotoRetriever {
    func queryLastPhoto(resizeTo size: CGSize?, queryCallback: (UIImage? -> Void)) {
        let fetchOptions = PHFetchOptions()
        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]

//        fetchOptions.fetchLimit = 1 // This is available in iOS 9.

        if let fetchResult = PHAsset.fetchAssetsWithMediaType(PHAssetMediaType.Image, options: fetchOptions) {
            if let asset = fetchResult.firstObject as? PHAsset {
                let manager = PHImageManager.defaultManager()

                // If you already know how you want to resize, 
                // great, otherwise, use full-size.
                let targetSize = size == nil ? CGSize(width: asset.pixelWidth, height: asset.pixelHeight) : size!

                // I arbitrarily chose AspectFit here. AspectFill is 
                // also available.
                manager.requestImageForAsset(asset,
                    targetSize: targetSize,
                    contentMode: .AspectFit,
                    options: nil,
                    resultHandler: { image, info in

                    queryCallback(image)
                })
            }
        }
    }
}
swilliams
  • 48,060
  • 27
  • 100
  • 130
  • This is great, would you mind showing how we use the struct method? I'm getting stuck on LastPhotoRetriever().queryLastPhoto(resizeTo: imagePickerButton.frame.size, queryCallback: UIImage? -> Void) – Nick Barr May 19 '16 at 18:38
8

Swift 3.0:

1) Import Photos framework in your header before your class declaration.

import Photos


2) Add the following method, which returns the last image.

func queryLastPhoto(resizeTo size: CGSize?, queryCallback: @escaping ((UIImage?) -> Void)) {
    let fetchOptions = PHFetchOptions()
    fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]

    let requestOptions = PHImageRequestOptions()
    requestOptions.isSynchronous = true

    let fetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
    if let asset = fetchResult.firstObject {
        let manager = PHImageManager.default()

        let targetSize = size == nil ? CGSize(width: asset.pixelWidth, height: asset.pixelHeight) : size!

        manager.requestImage(for: asset,
                             targetSize: targetSize,
                             contentMode: .aspectFit,
                             options: requestOptions,
                             resultHandler: { image, info in
                                queryCallback(image)
        })
    }

}


3) Then call this method somewhere in your app (maybe a button action):

@IBAction func pressedLastPictureAttachmentButton(_ sender: Any) {
    queryLastPhoto(resizeTo: nil){
        image in
        print(image)
    }
}
Josh O'Connor
  • 4,694
  • 7
  • 54
  • 98
6

To add to Art Gillespie's answer, using the fullResolutionImage uses the original image which — depending on the device's orientation when taking the photo — could leave you with an upside down, or -90° image.

To get the modified, but optimised image for this, use fullScreenImage instead....

 UIImage *img = [UIImage imageWithCGImage:[repr fullScreenImage]];
Liam
  • 1,028
  • 13
  • 24
1

Answer to the question (in Swift):

    func pickingTheLastImageFromThePhotoLibrary() {
        let assetsLibrary: ALAssetsLibrary = ALAssetsLibrary()
        assetsLibrary.enumerateGroupsWithTypes(ALAssetsGroupSavedPhotos,
            usingBlock: { (let group: ALAssetsGroup!, var stop: UnsafeMutablePointer<ObjCBool>) -> Void in
                if (group != nil) {
                    // Be sure to filter the group so you only get photos
                    group.setAssetsFilter(ALAssetsFilter.allPhotos())

                    group.enumerateAssetsWithOptions(NSEnumerationOptions.Reverse, 
                                 usingBlock: { (let asset: ALAsset!, 
                                                let index: Int, 
                                       var stop: UnsafeMutablePointer<ObjCBool>)
                                       -> Void in
                      if(asset != nil) {
                        /* 
                            Returns a CGImage representation of the asset.
                            Using the fullResolutionImage uses the original image which — depending on the
                            device's orientation when taking the photo — could leave you with an upside down,
                            or -90° image. To get the modified, but optimised image for this, use
                            fullScreenImage instead.
                        */
                        // let myCGImage: CGImage! = asset.defaultRepresentation().fullResolutionImage().takeUnretainedValue()

                        /*
                            Returns a CGImage of the representation that is appropriate for displaying full
                            screen.
                        */
                        // let myCGImage: CGImage! = asset.defaultRepresentation().fullScreenImage().takeUnretainedValue()

                        /* Returns a thumbnail representation of the asset. */
                        let myCGImage: CGImage! = asset.thumbnail().takeUnretainedValue()

                        // Here we set the image included in the UIImageView
                        self.myUIImageView.image = UIImage(CGImage: myCGImage)

                        stop.memory = ObjCBool(true)
                      }
                    })
                }

                stop.memory = ObjCBool(false)
            })
            { (let error: NSError!) -> Void in
                 println("A problem occurred: \(error.localizedDescription)")
            }
    }
King-Wizard
  • 15,628
  • 6
  • 82
  • 76
0

Using Photos Library ( Objective-C )

    PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];

PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:fetchOptions];
if (assetsFetchResult.count>0) {
    PHAsset *asset = [assetsFetchResult objectAtIndex:0];

    CGFloat scale = [UIScreen mainScreen].scale;
    CGFloat dimension = 55.0f; // set your required size
    CGSize size = CGSizeMake(dimension*scale, dimension*scale);

    [[PHImageManager defaultManager] requestImageForAsset:asset
                                               targetSize:size
                                              contentMode:PHImageContentModeAspectFit
                                                  options:nil
                                            resultHandler:^(UIImage *result, NSDictionary *info) {
                                                // do your thing with the image
                                            }
     ];
}
Art
  • 31
  • 2