How to Fetch Images from Photo Library Within Range of Two Dates in iOS

How to fetch images from photo library within range of two dates in iOS?

As per our conversation in comments in which you agreed to switch to Photos Framework instead of Assets Library, Rather than saving the images to your array, save the PHAsset's local identifier to your array.

Get the Local Identifiers of the images which lie in your date range

To get Images by Date, first create a utility method to create date, for reusability's sake:

-(NSDate*) getDateForDay:(NSInteger) day andMonth:(NSInteger) month andYear:(NSInteger) year{
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setDay:day];
[comps setMonth:month];
[comps setYear:year];
NSDate *date = [[NSCalendar currentCalendar] dateFromComponents:comps];
return date;
}

You can create startDate and endDate from it like this:

NSDate *startDate = [self getDateForDay:11 andMonth:10 andYear:2015];
NSDate *endDate = [self getDateForDay:15 andMonth:8 andYear:2016];

Now you need to get the FetchResults from Photo Library which exist between this range. Use this method for that:

-(PHFetchResult*) getAssetsFromLibraryWithStartDate:(NSDate *)startDate andEndDate:(NSDate*) endDate
{
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.predicate = [NSPredicate predicateWithFormat:@"creationDate > %@ AND creationDate < %@",startDate ,endDate];
PHFetchResult *allPhotos = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOptions];
return allPhotos;
}

Now you get the PHFetchResults of all the Photos which exist within this date range. Now to extract your Data Array of local identifiers, you can use this method:

-(NSMutableArray *) getAssetIdentifiersForFetchResults:(PHFetchResult *) result{

NSMutableArray *identifierArray = [[NSMutableArray alloc] init];
for(PHAsset *asset in result){
NSString *identifierString = asset.localIdentifier;
[identifierArray addObject:identifierString];
}
return identifierArray;
}

Add Methods to fetch/utilize the individual assets when you need them

Now, you will need the PHAsset for the image. You can use the LocalIdentifier like this to get PHAsset:

-(void) getPHAssetWithIdentifier:(NSString *) localIdentifier andSuccessBlock:(void (^)(id asset))successBlock failure:(void (^)(NSError *))failureBlock{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

NSArray *identifiers = [[NSArray alloc] initWithObjects:localIdentifier, nil];
PHFetchResult *savedAssets = [PHAsset fetchAssetsWithLocalIdentifiers:identifiers options:nil];
if(savedAssets.count>0)
{
successBlock(savedAssets[0]);
}
else
{
NSError *error;
failureBlock(error);
}
});
}

Then using this PHAsset, you can get the image for your required size (Try to keep it as minimum as possible to minimize memory usage):

-(void) getImageForAsset: (PHAsset *) asset andTargetSize: (CGSize) targetSize andSuccessBlock:(void (^)(UIImage * photoObj))successBlock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
PHImageRequestOptions *requestOptions;

requestOptions = [[PHImageRequestOptions alloc] init];
requestOptions.resizeMode = PHImageRequestOptionsResizeModeFast;
requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat;
requestOptions.synchronous = true;
PHImageManager *manager = [PHImageManager defaultManager];
[manager requestImageForAsset:asset
targetSize:targetSize
contentMode:PHImageContentModeDefault
options:requestOptions
resultHandler:^void(UIImage *image, NSDictionary *info) {
@autoreleasepool {

if(image!=nil){
successBlock(image);
}
}
}];
});

}

But don't call these method directly to get all the images you desire.

Instead, call these methods in your cellForItemAtIndexPath method like:

 //Show spinner
[self getPHAssetWithIdentifier:yourLocalIdentifierAtIndexPath andSuccessBlock:^(id assetObj) {
PHAsset *asset = (PHAsset*)assetObj;
[self getImageForAsset:asset andTargetSize:yourTargetCGSize andSuccessBlock:^(UIImage *photoObj) {
dispatch_async(dispatch_get_main_queue(), ^{
//Update UI of cell
//Hide spinner
cell.imgViewBg.image = photoObj;
});
}];
} failure:^(NSError *err) {
//Some error occurred in fetching the image
}];

Conclusion

So in conclusion:

  1. You can handle your memory issues by fetching only the images for visible cells instead of fetching the whole lot of them.
  2. You can optimize your performance by fetching the images on background threads.

If you want to get all your assets together anyways, you can get it using fetchAssetCollectionWithLocalIdentifiers: method though I will recommend against it.

Please drop a comment if you have any questions or if you have any other feedback.


Credits to Lyndsey Scott for setting predicate to PHFetchResult request for getting images between two dates in her answer here

Collect Photos of iPhone Library taken between two precise dates

To collect the photos between two dates, first you need to create NSDates representing the start and end of the date range. Here's a NSDate extension (from https://stackoverflow.com/a/24090354/2274694) that can create the dates from their string representations:

extension NSDate {
convenience
init(dateString:String) {
let dateStringFormatter = NSDateFormatter()
dateStringFormatter.dateFormat = "MM-dd-yyyy"
dateStringFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
let d = dateStringFormatter.dateFromString(dateString)!
self.init(timeInterval:0, sinceDate:d)
}
}

Then use the NSDates to create a predicate for the PHFetchResult's PHFetchOptions.

import Photos

class ViewController: UIViewController {

var images:[UIImage] = [] // <-- Array to hold the fetched images

override func viewDidLoad() {
fetchPhotosInRange(NSDate(dateString:"04-06-2015"), endDate: NSDate(dateString:"04-16-2015"))
}

func fetchPhotosInRange(startDate:NSDate, endDate:NSDate) {

let imgManager = PHImageManager.defaultManager()

let requestOptions = PHImageRequestOptions()
requestOptions.synchronous = true
requestOptions.networkAccessAllowed = true

// Fetch the images between the start and end date
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "creationDate > %@ AND creationDate < %@", startDate, endDate)

images = []

if let fetchResult: PHFetchResult = PHAsset.fetchAssetsWithMediaType(PHAssetMediaType.Image, options: fetchOptions) {
// If the fetch result isn't empty,
// proceed with the image request
if fetchResult.count > 0 {
// Perform the image request
for var index = 0 ; index < fetchResult.count ; index++ {
let asset = fetchResult.objectAtIndex(index) as! PHAsset
imgManager.requestImageDataForAsset(asset, options: requestOptions, resultHandler: { (imageData: NSData?, dataUTI: String?, orientation: UIImageOrientation, info: [NSObject : AnyObject]?) -> Void in
if let imageData = imageData {
if let image = UIImage(data: imageData) {
// Add the returned image to your array
self.images += [image]
}
}
if self.images.count == fetchResult.count {
// Do something once all the images
// have been fetched. (This if statement
// executes as long as all the images
// are found; but you should also handle
// the case where they're not all found.)
}
})
}
}
}
}
}

Updated for Swift 3:

import UIKit
import Photos

class ViewController: UIViewController {

var images:[UIImage] = [] // <-- Array to hold the fetched images

override func viewDidLoad() {
let formatter = DateFormatter()
formatter.dateFormat = "MM-dd-yyyy"
fetchPhotosInRange(startDate: formatter.date(from: "04-06-2015")! as NSDate, endDate: formatter.date(from: "04-16-2015")! as NSDate)
}

func fetchPhotosInRange(startDate:NSDate, endDate:NSDate) {

let imgManager = PHImageManager.default()

let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.isNetworkAccessAllowed = true

// Fetch the images between the start and end date
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "creationDate > %@ AND creationDate < %@", startDate, endDate)

images = []

let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
// If the fetch result isn't empty,
// proceed with the image request
if fetchResult.count > 0 {
// Perform the image request
for index in 0 ..< fetchResult.count {
let asset = fetchResult.object(at: index)
imgManager.requestImageData(for: asset, options: requestOptions, resultHandler: { (imageData: Data?, dataUTI: String?, orientation: UIImageOrientation, info: [AnyHashable : Any]?) -> Void in
if let imageData = imageData {
if let image = UIImage(data: imageData) {
// Add the returned image to your array
self.images += [image]
}
}
if self.images.count == fetchResult.count {
// Do something once all the images
// have been fetched. (This if statement
// executes as long as all the images
// are found; but you should also handle
// the case where they're not all found.)
print(self.images)
}
})
}
}
}
}

Swift: how to set range of photos between 2 dates?

There's a creationDate attribute, so you can extend your predicate to filter dates:

(creationDate >= %@) AND (creationDate <= %@)

So your predicate should look something like the following

options.predicate = NSPredicate(
format: "mediaType = %d AND (creationDate >= %@) AND (creationDate <= %@)",
PHAssetMediaType.image.rawValue,
fromDate as NSDate,
toDate as NSDate
)

PHFetchResults date filter not generating correct results for time range

The date created here would be in UTC timezone and yours may vary. Which is why the predicate may not return you correct results.

Make sure you have set the timezone of calendar to your system timezone before creating a date from it like:

NSCalendar* gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
[gregorian setTimeZone: [NSTimeZone systemTimeZone]];
NSDate *date = [gregorian dateFromComponents:comps];

That will create the date in your local timezone and generate correct results.

Fetch PHAssets in sequence

It is database, so returned as stored, but you can do it locally:

let fetchedResults = PHAsset.fetchAssets(withLocalIdentifiers: videosInLibrary, options: nil)

var tmpCache = [String: PHAsset]()
fetchedResults.enumerateObjects { (asset:PHAsset , count:Int, stop:UnsafeMutablePointer<ObjCBool>) in
tmpCache[asset.localIdentifier] = asset
}

let results = videosInLibrary.compactMap { tmpCache[$0] }

Get today's photo from album in IOS

You can use this snippet to get today's photo, which works on iOS 8. I originally filtered the assets from Recently Added album, which stores photos from last 30 days or 1000 photos. There is a chance user captures more than 1000 photos in two days, so I changed the code to get all photos from the library.

PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
options.predicate = [NSPredicate predicateWithFormat:@"mediaType = %d",PHAssetMediaTypeImage];

PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsWithOptions:options];

//get day component of today
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents *dayComponent = [calendar components:NSCalendarUnitDay fromDate:[NSDate date]];
NSInteger currentDay = dayComponent.day;

//get day component of yesterday
dayComponent.day = - 1;
NSDate *yesterdayDate = [calendar dateByAddingComponents:dayComponent toDate:[NSDate date] options:0];
NSInteger yesterDay = [[calendar components:NSCalendarUnitDay fromDate:yesterdayDate] day];

//filter assets of today and yesterday add them to an array.
NSMutableArray *assetsArray = [NSMutableArray array];
for (PHAsset *asset in assetsFetchResult) {
NSInteger assetDay = [[calendar components:NSCalendarUnitDay fromDate:asset.creationDate] day];

if (assetDay == currentDay || assetDay == yesterDay) {
[assetsArray addObject:asset];
}
else {
//assets is in descending order, so we can break here.
break;
}
}

Prior iOS 8, using ALAssetsLibrary, suppose you have a photo group, enumerate the group in reverse order, and do the similar thing as above.

[self.photoGroup enumerateAssetsWithOptions:NSEnumerationReverse usingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop) {
NSDate *date = [asset valueForProperty:ALAssetPropertyDate];
}];

Query IOS photo library based on location in Swift

Unfortunately the PHFetchOptions does not support querying for location. The supported keys can be seen in the documentation:

https://developer.apple.com/library/ios/documentation/Photos/Reference/PHFetchOptions_Class/index.html

The only possible solution for you is to fetch all photos and then filter the PHAssets in memory based on the location property:

https://developer.apple.com/library/ios/documentation/Photos/Reference/PHAsset_Class/index.html#//apple_ref/occ/instp/PHAsset/location



Related Topics



Leave a reply



Submit