PHImageManager requestImageForAsset returns nil sometimes for iCloud photos
I just went through this too. By my tests the issue appears on devices that have the "Optimize Storage" option enabled and resides in the difference between the two methods bellow:
[[PHImageManager defaultManager] requestImageForAsset: ...]
This will successfully fetch remote iCloud images if your options are correctly configured.
[[PHImageManager defaultManager] requestImageDataForAsset:...]
This function only works for images that reside on the phones memory or that were recently fetched from iCloud by your app on any other one.
Here's a working snippet I'm using -bear with me the Obj-c :)
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init]; options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; //I only want the highest possible quality options.synchronous = NO; options.networkAccessAllowed = YES; options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { NSLog(@"%f", progress); //follow progress + update progress bar }; [[PHImageManager defaultManager] requestImageForAsset:myPhAsset targetSize:self.view.frame.size contentMode:PHImageContentModeAspectFill options:options resultHandler:^(UIImage *image, NSDictionary *info) { NSLog(@"reponse %@", info); NSLog(@"got image %f %f", image.size.width, image.size.height); }];
Updated for Swift 4:
let options = PHImageRequestOptions() options.deliveryMode = PHImageRequestOptionsDeliveryMode.highQualityFormat options.isSynchronous = false options.isNetworkAccessAllowed = true options.progressHandler = { (progress, error, stop, info) in print("progress: \(progress)") } PHImageManager.default().requestImage(for: myPHAsset, targetSize: view.frame.size, contentMode: PHImageContentMode.aspectFill, options: options, resultHandler: { (image, info) in print("dict: \(String(describing: info))") print("image size: \(String(describing: image?.size))") })
I found that this had nothing to do with network or iCloud. It occasionally failed, even on images that were completely local. Sometimes it was images from my camera, sometimes it would be from images saved from the web.
I didn't find a fix, but a work around inspired by @Nadzeya that worked 100% of the time for me was to always request a target size equal to the asset size.
Eg.
PHCachingImageManager().requestImage(for: asset, targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight) , contentMode: .aspectFit, options: options, resultHandler: { (image, info) in if (image == nil) { print("Error loading image") print("\(info)") } else { view.image = image } });
I believe the drawbacks to this would be that we're getting the full image back in memory, and then forcing the ImageView to do the scaling, but at least in my use case, there wasn't a noticeable performance issue, and it was much better than loading a blurry or nil image.
A possible optimization here is to re-request the image at it's asset size only if the image comes back as nil.
I 've tried many things
- targetSize greater than (400, 400): not work
- targetSize equals to asset size: not work
- Disable
Optimize Storage
in iCloud Photos in Settings: not work - Dispatch
requestImage
to background queue: not work - Use
PHImageManagerMaximumSize
: not work - Use
isNetworkAccessAllowed
: not work - Play with different values in
PHImageRequestOptions
, likeversion
,deliveryMode
,resizeMode
: not work - Add a
progressHandler
: not work - Call
requestImage
again it cases it failed: not work
All I get is nil UIImage
and info with PHImageResultDeliveredImageFormatKey
, like in this radar Photos Frameworks returns no error or image for particular assets
Use aspect fit
What work for me, see https://github.com/hyperoslo/Gallery/blob/master/Sources/Images/Image.swift#L34
- Use
targetSize
with < 200: this is why I can load the thumbnail - Use
aspectFit
: Specify tocontentMode
does the trick for me
Here is the code
let options = PHImageRequestOptions()options.isSynchronous = trueoptions.isNetworkAccessAllowed = truevar result: UIImage? = nilPHImageManager.default().requestImage( for: asset, targetSize: size, contentMode: .aspectFit, options: options) { (image, _) in result = image}return result
Fetch asynchronously
The above may cause race condition, so make sure you fetch asynchronously, which means no isSynchronous
. Take a look at https://github.com/hyperoslo/Gallery/pull/72