52

How can I determine the largest PHAsset image size that is available on an iOS device, where by "available" I mean currently on the device, not requiring any network download from iCloud Photo Library?

I've been developing an app where a collection of thumbnails are shown in a UICollectionView (much like the Photos app) and then a user can select an image to see it full size. The full size image is downloaded from iCloud if necessary. However, I want to show the user the largest possible version of the image until the full size is available.

The way the Photos Framework works is rather odd:

In the collection view, I'm using a PHCachingImageManager to cache (and later request) images with a targetSize of 186x186 pixels (double the UICollectionViewCell dimensions because of retina scaling).

That works very well, with very fast scrolling and no lag in filling in the cells with images. Checking the size of the images returned, they are all roughly 342x256 pixels. That's on my iPhone 6 with iCloud Photo Library enabled and 'Optimize Storage' enabled, so only small thumbnails are stored on device for most images.

However, the odd part is that if I then request the full size (or much larger) version of any image, either with PHCachingImageManager or PHImageManager.defaultManager() using the PHImageRequestOptionsDeliveryMode.Opportunistic option, the first (low quality) image that is returned is a tiny 60x45 pixel postage stamp that looks horrible on the full screen, until eventually the full size image is downloaded to replace it.

Clearly there are larger thumbnails (342x256) on the device already, as they were just shown in the collection view! So why the hell doesn't it return them as the first 'low quality' image until the full size has been downloaded?? Just to make sure the 342x256 images really were already on the device I ran the code in airplane mode so no network access was happening.

What I found was that if I requested any size up to 256x256 I would get the 342x256 images returned. As soon as I went larger (even 1 pixel) it would first return me 60x45 images and then try to download the full size image.

I've changed my code to make a 256x256 request first, followed by a full size request with PHImageRequestOptionsDeliveryMode.HighQualityFormat (which doesn't return a tiny intermediate image), and that works beautifully. It's also what the iOS Photos app must be doing as it displays relatively high quality thumbnails whilst it's downloading the full image.

So, with that background, how do I know what the largest on device image size is for a PHAsset? Is 256x256 a magic number, or does it depend on how iCloud Photo Library is optimising for space (based on size of library and available device storage)?

Certainly seems like a bug that Photos Framework is not returning the largest image currently available (when asked for a size larger than it currently has), and that to get the largest size that it has, you have to ask for that size or smaller, because if you ask for larger, you'll get a 60x45 miniature! Bizarre.

UPDATE: Bug report submitted to Apple: https://openradar.appspot.com/25181601
Example project that demonstrates the bug: https://github.com/mluisbrown/PhotoKitBug

mluisbrown
  • 12,738
  • 6
  • 50
  • 81
  • While fetching thumbnails are you doing synchronous or asynchronous request with PHImageManager.defaultManager()? – Arun Ammannaya Jan 22 '16 at 07:17
  • @ArunAmmannaya I'm using a `PHCachingImageManager` to get the thumbnails, and the request is asynchronous. – mluisbrown Jan 24 '16 at 19:49
  • Then you are suppose to get multiple callback for single image with different sizes for requested image. How many times callback is getting called? – Arun Ammannaya Jan 25 '16 at 03:52
  • @ArunAmmannaya yes, I'm well aware of that, and I mention that in the question. Regardless, if there are 342x256 images already on the device, it makes no sense to return 60x45 images, especially if you've actually requested a larger size. As I say in the question, requesting up to 256x256 returns the largest thumbnail available (at least in my case), but requesting 257x257 (or larger) will suddenly drop you down to only getting a 60x45 thumbnail. – mluisbrown Jan 25 '16 at 11:15
  • I got similar problem. Images requested below a size are returned nil, and above that size are returned a full sized image. I tried networkAccessAllowed = true and got image nearly as requested. – BangOperator Jan 09 '18 at 08:06

3 Answers3

2

Using the example project posted in the question I was able to reproduce the issue in the simulator, but when I made the following change I would receive only the largest image:

let targetSize = PHImageManagerMaximumSize
markwatsonatx
  • 3,141
  • 1
  • 18
  • 19
  • Using `PHImageManagerMaximumSize` for the targetSize means get me the full resolution image, or nothing if it's not available. On the simulator, the full resolution image is always available. However, on a device, the full res images from of iCloud Photo Library are not usually available. Using `PHImageManagerMaximumSize` in the example project will not return any image at all because it's using the `networkAccessAllowed = false` option. So this is will not help I'm afraid :-( – mluisbrown Apr 09 '16 at 18:26
  • @mluisbrown Circling back to this. What is protocol here? Should I delete this answer, or keep it so others don't post the same answer (obviously that didn't really work)? – markwatsonatx Apr 24 '16 at 13:39
  • Mark, I think you should leave the answer. It was a valid suggestion. When I saw it I ran back to my Mac thinking "Could it really be that obvious?" Worth an up vote in fact :) – mluisbrown Apr 29 '16 at 20:04
0

Actually I don't know the reason why Apple would return the degraded 45*60 image with PHImageResultIsDegradedKey=1. For me, I just request the 256*256 image with deliveryMode PHImageRequestOptionsDeliveryModeHighQualityFormat at first, then I use the other PHImageRequestOptions to request full size image like below:

PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.networkAccessAllowed = YES;
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info){
// code to update the iCloud download progress
});

[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
// code to update UIImageView or something else.
});

And I think, Instagram may be use this similar solution to handle the image from iCloud.

Chris Forever
  • 628
  • 1
  • 5
  • 18
-3
[imageManager requestImageForAsset:asset
              targetSize: PHImageManagerMaximumSize
              contentMode:PHImageContentModeAspectFill
              options:nil
resultHandler:^(UIImage *result, NSDictionary *info)
{
    result;
}];
slfan
  • 8,209
  • 115
  • 61
  • 73
  • 2
    If you had bothered to read Mark Watson's answer you would know why this doesn't work (read my comment to that answer). – mluisbrown Apr 16 '16 at 11:31