26

So I am using the SwipeView library (https://github.com/nicklockwood/SwipeView) to show images using the Photos framework for iOS8.

However, when I call the requestImageForAsset I notice I am getting two results, a thumbnail size, and the bigger size that I want. However, the bigger image isn't loaded (it's called async I understand) in time to return, so it returns the small image.

This code might make more sense.

    func swipeView(swipeView: SwipeView!, viewForItemAtIndex index: Int, reusingView view: UIView!) -> UIView! {
            let asset: PHAsset = self.photosAsset[index] as PHAsset

    var imageView: UIImageView!

    let screenSize: CGSize = UIScreen.mainScreen().bounds.size
    let targetSize = CGSizeMake(screenSize.width, screenSize.height)


    var options = PHImageRequestOptions()
//        options.deliveryMode = PHImageRequestOptionsDeliveryMode.Opportunistic
    options.resizeMode = PHImageRequestOptionsResizeMode.Exact


    PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: targetSize, contentMode: .AspectFill, options: options, resultHandler: {(result, info)in
        println("huhuhuh")
        println(result.size)
        println(info)
        imageView = UIImageView(image: result)
    })
    println("setting view)
    return imageView
}

Here is the log output:

Enteredhuhuhuh
(33.5,60.0)
SETTING VIEW
huhuhuh
(320.0,568.0)

As you can see it returns the image view before the big image is recieved. How do I make it return this larger image so it's not showing the thumbnai?

Thanks.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Jonovono
  • 1,979
  • 7
  • 30
  • 53

8 Answers8

91

Read header of PHImageManager class

If -[PHImageRequestOptions isSynchronous] returns NO (or options is nil), resultHandler may be called 1 or more times. Typically in this case, resultHandler will be called asynchronously on the main thread with the requested results. However, if deliveryMode = PHImageRequestOptionsDeliveryModeOpportunistic, resultHandler may be called synchronously on the calling thread if any image data is immediately available. If the image data returned in this first pass is of insufficient quality, resultHandler will be called again, asychronously on the main thread at a later time with the "correct" results. If the request is cancelled, resultHandler may not be called at all. If -[PHImageRequestOptions isSynchronous] returns YES, resultHandler will be called exactly once, synchronously and on the calling thread. Synchronous requests cannot be cancelled. resultHandler for asynchronous requests, always called on main thread

So, what you want to do is that you make resultHandler to be called synchronously

PHImageRequestOptions *option = [PHImageRequestOptions new];
option.synchronous = YES;

[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:target contentMode:PHImageContentModeAspectFill options:option resultHandler:^(UIImage *result, NSDictionary *info) {
        //this block will be called synchronously
}];

So your block will be called before ending your method

Good luck!

Tony
  • 4,311
  • 26
  • 49
  • 1
    I think VietHung has the correct answer. To force the full resolution image to return, choose the Synchronous option. Otherwise, you can leave the code as is and display the low res thumbnail first and later allow the second call to the block to update the image in your image view. This is very likely the reason why Apple has chosen to execute the callback on the main thread so you can do any updates you want once the full res image is available. – Dan Loughney Dec 30 '14 at 21:57
  • 1
    If you go the latter, perhaps call setNeedsDisplay on the imageView in the block and see what happens. – Dan Loughney Dec 30 '14 at 22:04
  • 2
    This is what exactly i wanted. Super :) – Alvin Varghese Jan 06 '15 at 10:41
  • 1
    Nice! great explanation. – Mehul Sojitra Jul 08 '16 at 16:59
  • 1
    Thanks. Nice explanation. – Harshal Yanpallewar Jan 22 '20 at 11:27
4

By default the requestImageForAsset works as asynchronous. So in your method the return statement will be executed even before the image is retrieved. So my suggestion is instead of returning the imageView, pass the imageView in that you need to populate the image:

func swipeView(swipeView: SwipeView!, viewForItemAtIndex index: Int, reusingView view: UIView!, yourImageView: UIImageView)
{
    let asset: PHAsset = self.photosAsset[index] as PHAsset

    var imageView: UIImageView! = yourImageView;

    let screenSize: CGSize = UIScreen.mainScreen().bounds.size
    let targetSize = CGSizeMake(screenSize.width, screenSize.height)


    var options = PHImageRequestOptions()
    options.resizeMode = PHImageRequestOptionsResizeMode.Exact

    PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: targetSize, contentMode: .AspectFill, options: options, resultHandler: {(result, info)in
        imageView.image = result;
    })
}

Note:

Another option is you can fire a notification with the result image from the resultHandler. But I prefer the above mentioned method.

Refer PHImageManager for more information.

Midhun MP
  • 103,496
  • 31
  • 153
  • 200
4

resultHandler: block has info dictionary which may contain Boolean value for PHImageResultIsDegradedKey key, which indicates whether the result image is a low-quality substitute for the requested image.

Here's what documentation says:

PHImageResultIsDegradedKey: A Boolean value indicating whether the result image is a low-quality substitute for the requested image. (NSNumber)

If YES, the result parameter of your resultHandler block contains a low-quality image because Photos could not yet provide a higher-quality image. Depending on your settings in the PHImageRequestOptions object that you provided with the request, Photos may call your result handler block again to provide a higher-quality image.

O Fenômeno
  • 445
  • 3
  • 14
2

Check the Apple Documentation for this method here. In that they are saying:

For an asynchronous request, Photos may call your result handler block more than once. Photos first calls the block to provide a low-quality image suitable for displaying temporarily while it prepares a high-quality image. (If low-quality image data is immediately available, the first call may occur before the method returns.)

It might be taking time, in your case for fetching original size image. Otherwise your code seems okay to me.

Mrunal
  • 13,982
  • 6
  • 52
  • 96
2

You shouldn't rewrite imageView inside block, just set image to it and everything should work as you expect. To avoid showing two images you can check the size of image before assigning.

var imageView =  UIImageView()
...
...
        PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: targetSize, contentMode: .AspectFill, options: options, resultHandler: {(result, info)in
                println("huhuhuh")
                println(result.size)
                println(info)
                if result.size.width > 1000 &&  result.size.height > 1000 { // add any size you want 
                     imageView.setImage(result)
                }
            })
sage444
  • 5,661
  • 4
  • 33
  • 60
  • uhhh, that kind solution is a hack that is really unreliable, see the accepted answer for a good approach – Julian Jan 29 '16 at 13:09
0

try this will work for asynchronously too.

[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:target contentMode:PHImageContentModeAspectFill options:option resultHandler:^(UIImage *result, NSDictionary *info) {
    if ([info objectForKey:@"PHImageFileURLKey"]) {
      //your code
    }
}];
vaibby
  • 1,255
  • 1
  • 9
  • 23
0

Use below options, swift 3.0

publi var imageRequestOptions: PHImageRequestOptions{
let options = PHImageRequestOptions()
options.version = .current
options.resizeMode = .exact
options.deliveryMode = .highQualityFormat
options.isNetworkAccessAllowed = true
options.isSynchronous = true
return options}
Hiren Panchal
  • 2,963
  • 1
  • 25
  • 21
0

here is the best solution I had applied and it's working perfectly

let imageManager = PHImageManager.default()
        let requestOptions = PHImageRequestOptions()
    requestOptions.deliveryMode =  .highQualityFormat
    requestOptions.version = .current
    requestOptions.resizeMode = .exact
    requestOptions.isSynchronous = true

    imageManager.requestImage(for: self.assets.first!, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFit, options: nil, resultHandler: { image, info in
        if let info = info, info["PHImageFileUTIKey"] == nil {
            if let isDegraded = info[PHImageResultIsDegradedKey] as? Bool, isDegraded {
                //Here is Low quality image , in this case return
                print("PHImageResultIsDegradedKey =======> \(isDegraded)")
                return
            }
            //Here you got high resilutions image
            
        }
    })