requestImageDataForAsset returns nil image data - ios

I've been stuck with this for couple of days. I've bee trying to get the image within the call back but I always get nil. These the options which I used:
let options = PHImageRequestOptions()
options.deliveryMode = .HighQualityFormat
options.resizeMode = .None
I also tried with options set to nil without any luck. This is the data I got in the info value passed to the block.
[PHImageResultIsInCloudKey: 0,
PHImageResultDeliveredImageFormatKey: 9999,
PHImageFileURLKey: file:///var/mobile/Media/DCIM/100APPLE/IMG_0052.JPG,
PHImageResultRequestIDKey: 84,
PHImageResultIsDegradedKey: 0,
PHImageResultWantedImageFormatKey: 9999,
PHImageResultIsPlaceholderKey: 0,
PHImageFileSandboxExtensionTokenKey:
64b47b046511a340c57aa1e3e6e07994c1a13853;00000000;00000000;0000001a;com.apple.app-sandbox.read;;00000000;00000000;0000000000000000;/private/var/mobile/Media/DCIM/100APPLE/IMG_0051.JPG]
I also tried using requestImageForAsset and I got the same result. I thought by using requestImageDataForAsset I'll get more control on the data.
Also, I thought the file exists in the cloud, but it is not as PHImageResultIsInCloudKey value is set 0; otherwise, I'd download it.
by the way, I am able to get a smaller version of the image with predefined size of 200x200 inside another view; however, when I try to get the larger version of it, I get nil. I know that the image exists on the phone with higher res (I can see it in the Photos app)
Any help will be appreciated.

I had a similar issue and tried setting the networkAccessAllowed option on the PHImageRequestOptions object to YES - that seemed to fix it. For me the issue was definitely that the images were in the cloud as images on the camera roll worked fine but those in the cloud did not.

I have the same question. I think there is a bug in PHImageRequestOptions class , so we pass nil in the bellow code ,it's helpful for me.
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
assetModel.size = imageData.length;
NSString *filename = [asset valueForKey:#"filename"];
assetModel.fileName = filename;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

Related

How to generate thumbnails of images on iCloud Drive?

This appears easy, but the lack of documentation makes this question impossible to guess.
I have pictures and videos on my app's icloud drive and I want to create thumbnails of these assets. I am talking about assets on iCloud Drive, not the iCloud photo stream inside the camera roll. I am talking about the real iCloud Drive folder.
Creating thumbnails from videos are "easy" compared to images. You just need 2 weeks to figure out how it works, having in mind the poor documentation Apple wrote but thumbnails from images seem impossible.
What I have now is an array of NSMetadataItems each one describing one item on the iCloud folder.
These are the methods I have tried so far that don't work:
METHOD 1
[fileURL startAccessingSecurityScopedResource];
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
__block NSError *error;
[coordinator coordinateReadingItemAtURL:fileURL
options:NSFileCoordinatorReadingImmediatelyAvailableMetadataOnly
error:&error
byAccessor:^(NSURL *newURL) {
NSDictionary *thumb;
BOOL success = [newURL getResourceValue:&thumb forKey:NSURLThumbnailDictionaryKey error:&error];
UIImage *thumbnail = thumb[NSThumbnail1024x1024SizeKey];
}];
[fileURL stopAccessingSecurityScopedResource];
The results of this method are fantastic. Ready for that? Here we go: success = YES, error = nil and thumbnail = nil.
ANOTHER METHOD
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:fileURL
options:nil];
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
imageGenerator.appliesPreferredTrackTransform = YES;
CMTime time = CMTimeMake(0, 60); // time range in which you want
NSValue *timeValue = [NSValue valueWithCMTime:time];
[imageGenerator generateCGImagesAsynchronouslyForTimes:#[timeValue] completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * error) {
thumbnail = [[UIImage alloc] initWithCGImage:image];
}];
error = The requested URL was not found on this server. and thumbnail = nil
This method appears to be just for videos. I was trying this just in case. Any equivalent of this method to images?
PRIMITIVE METHOD
NSData *tempData = [NSData dataWithContentsOfUrl:tempURL];
NOPE - data = nil
METHOD 4
The fourth possible method would be using ALAsset but this was deprecated on iOS 9.
I think that all these methods fail because they just work (bug or not) if the resource is local. Any ideas on how to download the image so I can get the thumbnail?
Any other ideas?
thanks
EDIT: after several tests I see that Method 1 is the only one that seems to be in the right direction. This method works poorly, sometimes grabbing the icon but most part of the time not working.
Another point is this. Whatever people suggests me, they always say about downloading the whole image to get the thumbnail. I don't think this is the way to go. Just see how getting thumbnails of video work. You don't download the whole video to get its thumbnail.
So this
question remains open.
The Photos-Framework or AssetsLibrary will not work here as you would have to import your iCloud Drive Photos first to the PhotoLibrary to use any methods of these two frameworks.
What you should look at is ImageIO:
Get the content of the iCloud Drive Photo as NSData and then proceed like this:
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)(imageData), NULL );
NSDictionary* thumbOpts = [NSDictionary dictionaryWithObjectsAndKeys:
(id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailWithTransform,
(id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailFromImageAlways,
[NSNumber numberWithInt:160],(id)kCGImageSourceThumbnailMaxPixelSize,
nil];
CGImageRef thumbImageRef = CGImageSourceCreateThumbnailAtIndex(source,0,(__bridge CFDictionaryRef)thumbOpts);
UIImage *thumbnail = [[UIImage alloc] initWithCGImage: thumbImageRef];
After testing several solutions, the one that seems to work better is this one:
[fileURL startAccessingSecurityScopedResource];
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
__block NSError *error;
[coordinator coordinateReadingItemAtURL:fileURL
options:NSFileCoordinatorReadingImmediatelyAvailableMetadataOnly
error:&error
byAccessor:^(NSURL *newURL) {
NSDictionary *thumb;
BOOL success = [newURL getResourceValue:&thumb forKey:NSURLThumbnailDictionaryKey error:&error];
UIImage *thumbnail = thumb[NSThumbnail1024x1024SizeKey];
}];
[fileURL stopAccessingSecurityScopedResource];
This solution is not perfect. It will fail to bring the thumbnails sometimes but I was not able to find any other solution that works 100%. Others are worst than that.
This works for me.It has a little bit different
func genereatePreviewForOnce(at size: CGSize,completionHandler: #escaping (UIImage?) -> Void) {
_ = fileURL.startAccessingSecurityScopedResource()
let fileCoorinator = NSFileCoordinator.init()
fileCoorinator.coordinate(readingItemAt: fileURL, options: .immediatelyAvailableMetadataOnly, error: nil) { (url) in
if let res = try? url.resourceValues(forKeys: [.thumbnailDictionaryKey]),
let dict = res.thumbnailDictionary {
let image = dict[.NSThumbnail1024x1024SizeKey]
completionHandler(image)
} else {
fileURL.removeCachedResourceValue(forKey: .thumbnailDictionaryKey)
completionHandler(nil)
}
fileURL.stopAccessingSecurityScopedResource()
}
}
It looks like you are generating your thumbnail after the fact. If this is your document and you are using UIDocument, override fileAttributesToWriteToURL:forSaveOperation:error: to insert the thumbnail when the document is saved.

Cannot convert UIImage to NSData when fetched with PhotoKit

My scenario is that I select one of the photos fetched from system album with PhotoKit and presented in my UICollectionView and pass the selected photo (UIImage) to my next UIView and send it to remote server in form of NSData.
But when I put breakpoint before sending to track the NSData I found that the UIImage had data with allocated memory while NSData did not.
Here is the code to fetch the UIImage (Please notice that I didn't specify any PHImageRequestOptions object):
NSMutableArray *images = #[].mutableCopy;
PHImageManager *manager = [PHImageManager defaultManager];
PHAsset *asset = _photoPickerCollectionView.pickedAsset;
CGSize targetSize = CGSizeMake(asset.pixelWidth*0.5, asset.pixelHeight*0.5);
[manager requestImageForAsset:asset targetSize:targetSize contentMode:PHImageContentModeAspectFill options:nil resultHandler:^(UIImage *result, NSDictionary *info) {
_nextView.pickedOne = result;
}];
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
I convert the UIImage to NSData like this:
UIImage *image = _pickedOne;
NSData *imgData = UIImageJPEGRepresentation(image, imageQuality);
When I tracked the variables, the UIImage contains data as below:
But the NSData is nil as below:
And what was ODD is that if I specified an PHImageRequestOptions object for the PHImageManager to request images, the NSData wouldn't be nil. I'm not sure what was changed with or without the PHImageRequestOptions and why it would make such difference.
UPDATE:
What I found is that if you specify PHImageRequestOptions to nil then the default options will force it to fetch photos asynchronously which, in my opinion, can be unstable for NSData, so when I specify options.synchronous = YES; it would work.
But in this case, would it cause any retain cycle or some PhotoKit objects won't get released?
The method by default is asynchronous. You need to set options to explicitly make the image fetching synchronous. My strong suggestion for such kind of conversion is to use this API:
- (PHImageRequestID)requestImageDataForAsset:(PHAsset *)asset options:(nullable PHImageRequestOptions *)options resultHandler:(void(^)(NSData *__nullable imageData, NSString *__nullable dataUTI, UIImageOrientation orientation, NSDictionary *__nullable info))resultHandler;
This returns you the data directly but you will have to make this synchronous as well. This WON'T cause any retain cycles for any objects.
Hope this helps. :)

Photos.framework throws <Error>: ImageIO: PNG zlib error but still provides UIImage

I found a similar question here:
iOS 8 (Swift) How do I get rid of this error: ImageIO: PNG zlib error? however there are some differences.
I get the PHAsset from a picker - where the image does download and display properly (It uses the PHCachingImageManager class).
But, when I try to request the image later from the Asset like so:
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:AssetTargetSize
contentMode:PHImageContentModeAspectFit options:nil
resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
if (result)
_image = result;
}
the result variable is a pointer to a UIImage object but is not valid - I cannot turn it into NSData nor can I quicklook through xcode.
In the log I just get the following message: <Error>: ImageIO: PNG zlib error and nothing more.
I tried to make the request synchronous as suggested in the linked question, but it made no difference.
The image pointed at by the PHAsset is viewable both in the Photos app and the photo picker.
We need to enable synchronous option.
PHAsset *asset = array[i];
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.synchronous = YES;
PHImageManager *imageManager = [PHImageManager defaultManager];
[imageManager requestImageForAsset:asset targetSize:CGSizeMake(200, 200) contentMode:PHImageContentModeAspectFit options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
badge = (UIImage *)result;
}];
I resolved this by setting the aforementioned synchronous options to true as well as setting the size externally to the call to requestImageForAsset. Both were required, for me, for it to work. In Swift 2.x:
let aSize = CGFloat(200) // it's really a constant elsewhere in the project
let targetSize = CGSizeMake(aSize, aSize)
let options = PHImageRequestOptions()
options.synchronous = true
PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: targetSize, contentMode: .AspectFit, options: options, resultHandler: { (image, info) -> Void in
guard let imageFor = image else { return }
// use (now-unwrapped) imageFor here
})

iOS Photos framework unable to get image data / UTI

When requesting images from the Photos Framework I manage to get all but the last 64 correctly. The last ones always return nil for the dataUTI and imageData in the following code. Whilst attempting to figure out what was going on I found that the PHAsset knows exactly what the UTI is, but is reporting it to me as nil.
Anyone else seen this?
You can see I've made my code access the asset's UTI when it's reported as nil so that my app can determine if it's a gif or not but this isn't an advisable way of doing it and I never get the imageData anyway so it's not a huge amount of help!
PHFetchOptions* fetchOptions = [[PHFetchOptions alloc] init];
fetchOptions.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:NO]];
PHFetchResult *allPhotosResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options: fetchOptions];
[allPhotosResult enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop) {
PHImageRequestOptions* options = [[PHImageRequestOptions alloc] init];
options.synchronous = NO;
options.networkAccessAllowed = YES;
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
[[PHImageManager defaultManager] requestImageDataForAsset: asset options: options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
NSString* val = [asset valueForKey: #"uniformTypeIdentifier"];
if( !dataUTI )
{
dataUTI = val;
}
}];
}];
EDIT:
I forgot to mention that the missing image creation dates aren't the most recent images and seem spread out. Actually, even the Photos app doesn't seem to show them, based on their creation date. But there doesn't seem to be anything that should be in that their positions looking at the neighboring images of where their creation dates would place them.
Not much of an answer here so happy for someone else to take a bash at explaining it!
Looking at the creation dates of the missing assets I managed to track one down in the Photos app that was missing from my app. It had a thumbnail but when I selected it it did the circular download indicator to pull down the data but then trying to open it in my app's Action Extension (just let's you preview the gif's animation in the Photos app or elsewhere) a popup appeared that said there was an error preparing it. Which I've not seen before but clearly something was going wonky with iCloud.
Previously I was requesting the PHImageRequestOptionsVersionUnadjusted in my app but switching it to PHImageRequestOptionsVersionOriginal seems to have fixed it....?

Photos Framework requestImageDataForAsset occasionally fails

I'm using the photos framework on iOS8.1 and requesting the image data for the asset using requestImageDataForAsset... Most of the time it works and I get the image data and a dictionary containing what you see below. But sometimes the call completes, but the data is nil and the dictionary contains three generic looking entries.
The calls are performed sequentially and on the same thread. It is not specific to any particular image. The error will happen on images I've successfully opened in the past. Has anyone encountered this?
+ (NSData *)retrieveAssetDataPhotosFramework:(NSURL *)urlMedia resolution:(CGFloat)resolution imageOrientation:(ALAssetOrientation*)imageOrientation {
__block NSData *iData = nil;
PHFetchResult *result = [PHAsset fetchAssetsWithALAssetURLs:#[urlMedia] options:nil];
PHAsset *asset = [result firstObject];
PHImageManager *imageManager = [PHImageManager defaultManager];
PHImageRequestOptions *options = [[PHImageRequestOptions alloc]init];
options.synchronous = YES;
options.version = PHImageRequestOptionsVersionCurrent;
#autoreleasepool {
[imageManager requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
iData = [imageData copy];
NSLog(#"requestImageDataForAsset returned info(%#)", info);
*imageOrientation = (ALAssetOrientation)orientation;
}];
}
assert(iData.length != 0);
return iData;
}
This is the desired result where I get image data and the dictionary of meta data:
requestImageDataForAsset returned info({
PHImageFileDataKey = <PLXPCShMemData: 0x1702214a0> bufferLength=1753088 dataLength=1749524;
PHImageFileOrientationKey = 1;
PHImageFileSandboxExtensionTokenKey = "6e14948c4d0019fbb4d14cc5e021199f724f0323;00000000;00000000;000000000000001a;com.apple.app-sandbox.read;00000001;01000003;000000000009da80;/private/var/mobile/Media/DCIM/107APPLE/IMG_7258.JPG";
PHImageFileURLKey = "file:///var/mobile/Media/DCIM/107APPLE/IMG_7258.JPG";
PHImageFileUTIKey = "public.jpeg";
PHImageResultDeliveredImageFormatKey = 9999;
PHImageResultIsDegradedKey = 0;
PHImageResultIsInCloudKey = 0;
PHImageResultIsPlaceholderKey = 0;
PHImageResultWantedImageFormatKey = 9999;
})
Here's what I get occasionally. image data is nil. Dictionary contains not so much.
requestImageDataForAsset returned info({
PHImageResultDeliveredImageFormatKey = 9999;
PHImageResultIsDegradedKey = 0;
PHImageResultWantedImageFormatKey = 9999;
})
I had a problem with similar symptoms where requestImageDataForAsset returned nil image data but was also accompanied by a console error message like this:
[Generic] Failed to load image data for asset <PHAsset: 0x13d041940> 87CCAFDC-A0E3-4AC9-AD1C-3F57B897A52E/L0/001 mediaType=1/0, sourceType=2, (113x124), creationDate=2015-06-29 04:56:34 +0000, location=0, hidden=0, favorite=0 with format 9999
In my case, the problem suddenly started happening on a specific device only with assets in iCloud shared albums after upgrading from iOS 10.x to 11.0.3, and since then through to 11.2.5. Thinking that maybe requestImageDataForAsset was trying to use files locally cached in /var/mobile/Media/PhotoData/PhotoCloudSharingData/ (from the info dictionary's PHImageFileURLKey key) and that the cache may be corrupt I thought about how to clear that cache.
Toggling the 'iCloud Photo Sharing' switch in iOS' Settings -> Accounts & Passwords -> iCloud -> Photos seems to have done the trick. requestImageDataForAsset is now working for those previously failing assets.
Update 9th March 2018
I can reproduce this problem now. It seems to occur after restoring a backup from iTunes:
Use the iOS app and retrieve photos from an iCloud shared album.
Backup the iOS device using iTunes.
Restore the backup using iTunes.
Using the app again to retrieve the same photos from the iCloud shared album now fails with the above console message.
Toggling the 'iCloud Photo Sharing' switch fixes it still. Presumably the restore process somehow corrupts some cache. I've reported it as Bug 38290463 to Apple.
You are likely iterating through an array, and memory is not freed timely, you can try the below code. Make sure theData is marked by __block.
#autoreleasepool {
[imageManager requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
NSLog(#"requestImageDataForAsset returned info(%#)", info);
theData = [imageData copy];
}];
}
Getting back to this after a long while, I have solved a big part of my problem. No mystery, just bad code:
PHFetchResult *result = [PHAsset fetchAssetsWithALAssetURLs:#[urlMedia] options:nil];
PHAsset *asset = [result firstObject];
if (asset != nil) { // the fix
PHImageManager *imageManager = [PHImageManager defaultManager];
PHImageRequestOptions *options = [[PHImageRequestOptions alloc]init];
...
}
The most common cause for me was a problem with the media URL passed to fetchAssetsWithALAssetURLs causing asset to be nil and requestImageDataForAsset return a default info object.
The following code maybe help. I think the class PHImageRequestOptions has a bug, so I pass nil , and then fix the bug.
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[PHImageManager defaultManager] requestImageDataForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
assetModel.size = imageData.length;
NSString *filename = [asset valueForKey:#"filename"];
assetModel.fileName = filename;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

Resources