Cannot get a clear image requested from Photo Album using PhotoKit - ios

I am using PhotoKit to fetch the photos in one of the system albums like this:
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:YES]];
PHFetchResult *fetchResult = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
[fetchResult enumerateObjectsUsingBlock:^(PHAssetCollection *collection, NSUInteger idx, BOOL *stop) {
NSLog(#"ALBUM NAME: %#", collection.localizedTitle);
if ([collection.localizedTitle isEqualToString:#"Camera Roll"]) {
PHFetchResult *photos = [PHAsset fetchAssetsInAssetCollection:collection options:nil];
NSLog(#"PHOTOS: %ld", photos.count);
_photos = nil;
_photos = #[].mutableCopy;
for (PHAsset *asset in photos) {
[self loadImageFromPHAsset:asset];
}
}
}];
Then in my custom helper method: (void)loadImageFromPHAsset:(PHAsset *)asset, I have this:
-(void)loadImageFromPHAsset:(PHAsset *)asset
{
PHImageManager *manager = [PHImageManager defaultManager];
CGSize targetSize = _layout.itemSize;
[manager requestImageForAsset:asset targetSize:targetSize contentMode:PHImageContentModeAspectFill options:nil resultHandler:^(UIImage *result, NSDictionary *info) {
[_photos addObject:result];
}];
[self.collectionView reloadData];
}
So I have an array of images and I present them in my UICollectionViewCell:
UIImage *image = [_photos objectAtIndex:indexPath.row];
cell.imageView.contentMode = UIViewContentModeScaleAspectFill;
cell.imageView.image = image;
But what I got is like this, it is very blur, how can I make it clear?

First of all the targetSize should be multiplied by scale to account for the screen's scale like this,
CGFloat scale = [UIScreen mainScreen].scale;
CGSize targetSize = CGSizeMake(_layout.itemSize.width*scale, _layout.itemSize.height*scale);
Secondly, try these options to get the exact size image,
//Async call returned on main thread, can return multiple times
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.resizeMode = PHImageRequestOptionsResizeModeExact;
Lastly, if you have performance concerns, use PHCachingManager, I have answered the question regarding performance improvements here
Hope this helps you.
Edit:
I did not realise that the images were stored in an array. Try setting the synchronous flag in image options.
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.synchronous = YES;
options.resizeMode = PHImageRequestOptionsResizeModeExact;

Related

IS PHAsset image unique globally for all iPhones?

When fetching images using the Photo framework, I am getting all images of the gallery from the PHAsset object. My question is if the images from PHAsset are globally unique or are they device specific. Can I use the same PHAsset image on different iPhones? i don't want to convert the UIImage to NSData and convert it again from NSData to UIImage.
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
//set up fetch options, mediaType is image.
PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:NO]];
options.predicate = [NSPredicate predicateWithFormat:#"mediaType = %d",PHAssetMediaTypeImage];
for (NSInteger i =0; i < smartAlbums.count; i++) {
PHAssetCollection *assetCollection = smartAlbums[i];
PHFetchResult *assetsFetchResult = [PHAsset fetchAssetsInAssetCollection:assetCollection options:options];
NSLog(#"sub album title is %#, count is %ld", assetCollection.localizedTitle, (unsigned long)assetsFetchResult.count);
if (assetsFetchResult.count > 0 && ![assetCollection.localizedTitle isEqualToString: #"Recently Deleted"]) {
for (PHAsset *asset in assetsFetchResult) {
PHImageRequestOptions *option = [PHImageRequestOptions new];
option.synchronous = YES;
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:CGSizeMake(self.view.frame.size.width, self.view.frame.size.height-100) contentMode:PHImageContentModeAspectFit options:option resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
[mutArrAsset addObject:asset];
[self.imagesURLArray addObject:result];
}];
NSLog(#"asset are %#", asset);
}}}
A PHAsset represent a pointer to an image in the user's photo library. People have different images on their phone so it doesn't make sense to pass that pointer to a different phone. PHAsset's id is explicted called localIdentifier for this reason.
If you want to transfer an image from one device to a different device you have to transfer it as NSData.

Replacing ALAssertLibrary to get the filename

Im using the following code to access filename of the image that I gotta upload. I need both filename alongside the file path and size.
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *imageAsset)
{
ALAssetRepresentation *imageRep = [imageAsset defaultRepresentation];
NSLog(#"[imageRep filename] : %#", [imageRep filename]);
[_imageNameArray addObject:[imageRep filename]];
};
[_uploadTbleView reloadData];
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL:refURL resultBlock:resultblock failureBlock:nil];
The problem that Im facing is,
Xcode throws a warning message that ALAssetsLibraryAssetForURLResultBlock is deprecated. How can I replace the above code to get the filename ?
I tried using PHAsset but every time when Im selecting the file, it has got the same file name called asset.jpg for all image files.
You can try this:
PHAsset *asset = nil;
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:YES]];
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:fetchOptions];
if (fetchResult != nil && fetchResult.count > 0) {
// get last photo from Photos
asset = [fetchResult lastObject];
}
if (asset) {
// get photo info from this asset
PHImageRequestOptions * imageRequestOptions = [[PHImageRequestOptions alloc] init];
imageRequestOptions.synchronous = YES;
[[PHImageManager defaultManager]
requestImageDataForAsset:asset
options:imageRequestOptions
resultHandler:^(NSData *imageData, NSString *dataUTI,
UIImageOrientation orientation,
NSDictionary *info)
{
NSLog(#"info = %#", info);
if ([info objectForKey:#"PHImageFileURLKey"]) {
// path looks like this -
// file:///var/mobile/Media/DCIM/###APPLE/IMG_####.JPG
NSURL *path = [info objectForKey:#"PHImageFileURLKey"];
}
}];
}

[PHCollectionList canContainCustomKeyAssets]: unrecognized selector sent to instance

Fetching PHAssets is now crashing for me on latest versions of iOS (iOS 9.2 and iOS 9.3). It previously worked fine.
The error I am getting is:
[PHCollectionList canContainCustomKeyAssets]: unrecognized selector sent to instance
Terminating app due to uncaught exception 'NSInvalidArgumentException'
The line throwing the exception is:
PHFetchResult *fetchImage = [PHAsset fetchKeyAssetsInAssetCollection:(PHAssetCollection*)collection options:fetchOptions];
Here is more code, for reference:
Class PHPhotoLibrary_class = NSClassFromString(#"PHPhotoLibrary");
if (PHPhotoLibrary_class) {
PHFetchResult *fetchResult = self.collectionsFetchResults[indexPath.section];
PHCollection *collection = fetchResult[indexPath.row];
cell.textLabel.text = collection.localizedTitle;
PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:YES]];
PHFetchResult *fetchImage = [PHAsset fetchKeyAssetsInAssetCollection:(PHAssetCollection*)collection options:fetchOptions];
PHAsset *asset = [fetchImage firstObject];
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.resizeMode = PHImageRequestOptionsResizeModeExact;
CGFloat scale = [UIScreen mainScreen].scale;
CGFloat dimension = 90.0;
CGSize size = CGSizeMake(dimension*scale, dimension*scale);
[[PHImageManager defaultManager] requestImageForAsset:asset
targetSize:size
contentMode:PHImageContentModeAspectFill
options:options
resultHandler:^(UIImage *result, NSDictionary *info) {
if (result) {
CGSize itemSize = CGSizeMake(60, 60);
UIGraphicsBeginImageContextWithOptions(itemSize, NO, 2);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[result drawInRect:imageRect];
cell.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
else{
UIImage *placeholder = [UIImage imageNamed:#"image-placeholder.jpg"];
CGSize itemSize = CGSizeMake(60, 60);
UIGraphicsBeginImageContextWithOptions(itemSize, NO, 2);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[placeholder drawInRect:imageRect];
cell.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
}];
}
This issue actually lies here:
PHCollection *collection = fetchResult[indexPath.row];
and then here:
PHAsset *asset = [fetchImage firstObject];
You are fetching the collection using PHCollection and then just assuming all assets are PHAssets without properly checking if this is the case or not.
Actually, PHCollection has two possible subclasses: PHAsset and PHCollectionList, and the PHCollectionList is what is throwing the error here.
Wrap the code after PHCollection *collection = fetchResult[indexPath.row]; with a check for PHAsset, and it should solve the issue:
if ([collection isKindOfClass:[PHAssetCollection class]]) {
//code
}
First, get all assets using PHAsset like
dispatch_async(dispatch_get_main_queue(), ^{
AllPhotos=[[NSMutableArray alloc] init];
PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];
for (PHAsset *asset in result)
{
[AllPhotos addObject:asset];
}
[self.collectionView reloadData];
// code here
});
Not, use the collectionview delegate method like
PHImageManager *manager = [PHImageManager defaultManager];
PHAsset *asset = [AllPhotos objectAtIndex:indexPath.row];
PHImageRequestOptions *requestOptions = [[PHImageRequestOptions alloc] init];
requestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;
requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeOpportunistic;
requestOptions.synchronous = false;
//CGFloat scale = 1.5; //or an even smaller one
if (!manager)
{
manager = [[PHCachingImageManager alloc] init];
}
if (!requestOptions)
{
requestOptions = [[PHImageRequestOptions alloc] init];
}
[manager requestImageForAsset:asset
targetSize:CGSizeMake(imagesize.width,imagesize.height)
contentMode:PHImageContentModeAspectFill
options:requestOptions
resultHandler:^void(UIImage *image, NSDictionary *info)
{
[imageview setImage:image];
}];
[cell.contentView addSubview:imageview];

Can't read some files from camera roll

I wrote simple library to get photos from camera roll. Unfortunately can't read some of them. I can't preview or convert to NSData
PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.includeAssetSourceTypes = PHAssetSourceTypeUserLibrary;
PHFetchResult *allPhotosResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:options];
PHImageRequestOptions *requestOptionForPhotos = [[PHImageRequestOptions alloc] init];
requestOptionForPhotos.networkAccessAllowed = YES;
for(PHAsset *asset in allPhotosResult) {
[[PHImageManager defaultManager]
requestImageForAsset:asset
targetSize:CGSizeMake(100, 100)
contentMode:PHImageContentModeAspectFill
options:requestOptionForPhotos
resultHandler:^(UIImage *result, NSDictionary *info) {
NSData *data = UIImagePNGRepresentation(result);
NSString *base = [data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; // for some of photos there is nil
}];
}

Get filesize and other information of asset without fully loading it into memory

I need to get information about an asset without loading it into memory which might take a long time if the asset is very large.
As I will be iterating through all assets available in my device, it might take a very long time.
I am using the following code to load an asset in to an NSData object and also retrieve some information about it:
- (NSData*)getAssetInfo: (NSUInteger*) assetIndex {
PHFetchOptions *fetchOptions;
fetchOptions.sortDescriptors = #[ [NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:YES], ];
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithOptions:fetchOptions];
__block NSData *iData = nil;
PHAsset *asset = [fetchResult objectAtIndex: assetIndex];
PHImageManager *imageManager = [PHImageManager defaultManager];
PHImageRequestOptions *options = [[PHImageRequestOptions alloc]init];
options.synchronous = YES;
options.version = PHImageRequestOptionsVersionCurrent;
#autoreleasepool {
[imageManager requestImageDataForAsset:asset options:options resultHandler:^(NSData *imgData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
//NSLog(#"requestImageDataForAsset returned info(%#)", info);
NSURL *url = [info valueForKey: #"PHImageFileURLKey"];
NSString *urlAsString = [url absoluteString];
gAssetFilename = [urlAsString lastPathComponent];
gAssetCreationDate = asset.creationDate;
iData = imgData;
}];
}
if (iData != nil) {
return iData;
}
else{
NSLog(#"OOPS!");
return nil;
}
}
This works but as I said, it might take a very long time.
Is it possible to get the size of the asset as well other information about the asset without first fully load it into memory?

Resources