iosswiftios8.3photosframework

Requesting images to PHImageManager results in wrong image in iOS 8.3


Context

I'm using the Photos framework to display user pictures in an iOS application I'm working on.

For reference, I uploaded a few images to imgur, where you can find the original images I'm testing with, and screenshots I took of an UIImageView with pink background displaying the fetched images with "Aspect Fit" configuration. Here's the link: https://i.sstatic.net/beugu.jpg

I'm using two test scenarios: first, a landscape image, whose imageOrientation is UIImageOrientation.Up (0); secondly, a portrait image, with UIImageOrientation.Right (3). You can check in the documentation (I would put the link here, but I don't have enough reputation, sorry).

Problem

The iPhone 4S (iOS 8.1.2) displays the landscape image in a landscape layout inside the pink UIImageView, and the portrait with a portrait layout, as expected. But the iPhone 5 and the 6 Plus (both iOS 8.3) display both images in a landscape layout.

Code

This is the code that I'm using to request the images:

var targetSize = CGSizeMake(1000, (CGFloat(asset.pixelHeight) / CGFloat(asset.pixelWidth)) * 1000)

var options = PHImageRequestOptions()
options.version = PHImageRequestOptionsVersion.Current
options.deliveryMode =  PHImageRequestOptionsDeliveryMode.HighQualityFormat
options.resizeMode = PHImageRequestOptionsResizeMode.Exact
options.networkAccessAllowed = true

PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: targetSize, contentMode: PHImageContentMode.AspectFill, options: options) { (result: UIImage!, info) -> Void in
    if !info[PHImageResultIsDegradedKey]!.boolValue {
        println("asset.pixelWidth: \(asset.pixelWidth)")
        println("asset.pixelHeight: \(asset.pixelHeight)")
        println("targetSize: \(targetSize)")
        println("result.size: \(result.size)")
        println("result.imageOrientation: \(result.imageOrientation.rawValue)")

        completion(result)
    }
}

Tests

Here are the logs I get when executing tests with those two images:

Case 1: iPhone 4S (8.1.2)

Landscape:

asset.pixelWidth: 3264
asset.pixelHeight: 2448
targetSize: (1000.0, 750.0)
result.size: (1000.0, 750.0)
result.imageOrientation: 0

Portrait:

asset.pixelWidth: 2448
asset.pixelHeight: 3264
targetSize: (1000.0, 1333.33)
result.size: (1000.0, 1332.0)
result.imageOrientation: 3

Case 2: iPhone 5 (8.3)

Landscape:

asset.pixelWidth: 3264
asset.pixelHeight: 2448
targetSize: (1000.0, 750.0)
result.size: (1000.0, 750.0)
result.imageOrientation: 0

Portrait:

asset.pixelWidth: 2448
asset.pixelHeight: 3264
targetSize: (1000.0, 1333.33)
result.size: (1334.0, 1000.0)
result.imageOrientation: 3

Case 3: iPhone 6 Plus (8.3)

Landscape:

asset.pixelWidth: 3264
asset.pixelHeight: 2448
targetSize: (1000.0, 750.0)
result.size: (1000.0, 750.0)
result.imageOrientation: 0

Portrait:

asset.pixelWidth: 2448
asset.pixelHeight: 3264
targetSize: (1000.0, 1333.33333333333)
result.size: (1334.0, 1000.0)
result.imageOrientation: 3

Conclusion

You can see by the logs that the iOS 8.1.2 version respects the targetSize specified for both images, but the 8.3 versions are fetching images with inverted dimensions (requested (1000.0, 1333.33) but received (1334.0, 1000.0)).

Right now, it seems like my only option is to check which iOS version is running, and then invert the parameters when requesting an image with rotated orientations (I only tested with UIImageOrientation.Right, but there's also .Left, .LeftMirrored and .RightMirrored).

Unfortunately, the PHAsset doesn't contain the image's orientation. I can only know it after requesting the image to the PHImageManager, so I'll have to make two requests for those images, one to obtain the orientation and another to get the correct image.

Question

Is that an issue with my logic? Or is that a bug in iOS? If it's a bug, which one is the correct behaviour, the iOS 8.3's one or the older versions'? Can anyone offer other options to handling with this problem?

Sorry for the long post, and for the bad English. Thanks in advance!


Solution

  • I made a bizarre workaround:

    func getAssetImage(asset: PHAsset, completion: (UIImage?) -> Void) {
        getAssetImage(asset, firstTry: true, completion: completion)
    }
    
    private func getAssetImage(asset: PHAsset, firstTry: Bool, completion: (UIImage?) -> Void) {
        let targetSize = CGSizeMake(1000, (CGFloat(asset.pixelHeight) / CGFloat(asset.pixelWidth)) * 1000)
    
        let options = PHImageRequestOptions()
        options.version = PHImageRequestOptionsVersion.Current
        options.deliveryMode =  PHImageRequestOptionsDeliveryMode.HighQualityFormat
        options.resizeMode = PHImageRequestOptionsResizeMode.Exact
        options.networkAccessAllowed = true
    
        PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: targetSize, contentMode: PHImageContentMode.AspectFill, options: options) { (result: UIImage?, info) -> Void in
            let degraded = info?[PHImageResultIsDegradedKey] as? NSNumber
    
            if degraded != nil && !degraded!.boolValue {
                print("asset.pixelWidth: \(asset.pixelWidth)")
                print("asset.pixelHeight: \(asset.pixelHeight)")
                print("targetSize: \(targetSize)")
                print("result.size: \(result!.size)")
                print("result.imageOrientation: \(result!.imageOrientation.rawValue)")
    
                if (targetSize.width - targetSize.height) * (result!.size.width - result!.size.height) < 0 {
                    if firstTry {
                        print("Resulting image's dimensions are not in accordance with target size. Reversing dimensions!")
                        self.getAssetImage(asset, firstTry: false, completion: completion)
                    } else {
                        print("Resulting image's dimensions are still not in accordance with target size. Giving up!")
                        completion(nil)
                    }
    
                } else {
                    completion(result)
                }
            }
        }
    }
    

    The performance of this solution is quite questionable, but it was one of the most-reliable ways to deal with this problem.