PHImageManager请求iCloud照片的图片时有时会返回空值。

59

大约有10%的时间,PHImageManager.defaultManager().requestImageForAsset在首先返回有效但“降级”的UIImage后,返回nil而不是一个有效的UIImage。我无法在nil中看到任何错误或其他提示。

这似乎发生在需要从iCloud下载照片时,启用了iCloud照片库和优化iPad存储。我已经尝试更改选项、大小等,但似乎都没有影响。

如果在失败后重试requestImageForAsset请求,通常会正确返回UIImage,但有时需要多次重试。

我做错了什么?还是这只是Photos框架中的一个bug?

    func photoImage(asset: PHAsset, size: CGSize, contentMode: UIViewContentMode, completionBlock:(image: UIImage, isPlaceholder: Bool) -> Void) -> PHImageRequestID? {

    let options = PHImageRequestOptions()
    options.networkAccessAllowed = true
    options.version = .Current
    options.deliveryMode = .Opportunistic
    options.resizeMode = .Fast

    let requestSize = !CGSizeEqualToSize(size, CGSizeZero) ? size : PHImageManagerMaximumSize
    let requestContentMode = contentMode == .ScaleAspectFit ? PHImageContentMode.AspectFit : PHImageContentMode.AspectFill

    return PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: requestSize, contentMode: requestContentMode, options: options)
        { (image: UIImage!, info: [NSObject : AnyObject]!) in
            if let image = image {
                let degraded = info[PHImageResultIsDegradedKey] as? Bool ?? false
                completionBlock(image: photoBlock.rotatedImage(image), isPlaceholder: degraded)

            } else {
                let error = info[PHImageErrorKey] as? NSError
                NSLog("Nil image error = \(error?.localizedDescription)")
           }
    }
}

我有完全相同的问题,你找到任何解决方案了吗? - dlinsin
1
我也遇到了同样的问题。我认为这是一个 bug,并已经提交了一个 bug 报告。 - Jordan H
4
当我将 options.deliveryMode 设为 HighQualityFormat 时,我看到了这种情况发生,但是当我将它设为 Opportunistic 时却没有。 - Alfie Hanssen
@AlfieHanssen,对我来说没有任何区别。 - Jonathan Crooke
有人能告诉我如何解决这个问题吗?https://stackoverflow.com/questions/55488998/how-to-fix-empty-asset-issue-with-phimagemanager-in-assetspickercontroller-ios?我也遇到了视频的同样问题。 - user2786
@dlinsin,Jordan H,Lenk,你们有没有解决这个问题的方案? - Dixit Akabari
11个回答

53

我也经历过这个问题。根据我的测试,此问题出现在启用“优化存储”选项的设备上,并且存在于以下两种方法之间的差异中:

[[PHImageManager defaultManager] requestImageForAsset: ...]

如果您的选项已正确配置,则可以成功获取远程iCloud图像。


[[PHImageManager defaultManager] requestImageDataForAsset:...]

此函数仅适用于存储在手机内存中或最近由您的应用程序从iCloud获取的图像。


下面是我正在使用的有效片段 - 请理解一下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);
    }];

在 Github 上查看完整代码

更新至 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))")
    })

8
我不太明白这个方法是如何解决原始问题的? - dlinsin
1
我同意dlinsin的观点,这看起来和我正在做的一样,但仍然经常失败。 - LenK
1
我一直在使用它,从未出现过故障。这也可能是框架中的一个错误,因为它仍然不稳定。 - ahbou
2
这不是针对iCloud Drive的。 它是用于从iCloud照片库中获取照片的。 - ahbou
16
复杂的部分在于 networkAccessAllowed。 根据文档,“如果必要,将从iCloud下载图像”。默认情况下,它被设置为false。 - James Chen
显示剩余7条评论

8

上述方法都对我不起作用,但这个解决方案有效!

private func getUIImage(asset: PHAsset, retryAttempts: Int = 10) -> UIImage? {
    var img: UIImage?
    let manager = PHImageManager.default()
    let options = PHImageRequestOptions()
    options.version = .original
    options.isSynchronous = true
    options.isNetworkAccessAllowed = true
    manager.requestImage(for: asset, targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight), contentMode: .aspectFit, options: options, resultHandler: { image, _ in
        img = image
    })
    if img == nil && retryAttempts > 0 {
        return getUIImage(asset: asset, retryAttempts: retryAttempts - 1)
    }
    return img
}

这里的区别在于递归重试。这个方法在我使用的所有情况下都能百分之百地成功。


2
options.isNetworkAccessAllowed = true 帮到你了!谢谢!=) - chestozo
1
options.isNetworkAccessAllowed = true 也解决了我的问题。 - sabiland

7

我发现这与网络或iCloud无关,有时甚至在完全本地的图像上也会偶尔失败。有时是来自我的相机的图像,有时则是从网页保存的图像。

我没有找到解决方法,但受@Nadzeya启发,我找到了一个100%有效的解决方法,就是始终请求目标大小等于资产大小

例如:

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
        }
    });

我认为这种方法的缺点是我们需要将整个图像加载到内存中,然后强制ImageView进行缩放,但至少在我的使用情况下,没有明显的性能问题,而且它比加载模糊或nil图像要好得多。
一种可能的优化方法是,如果图像返回为nil,则仅重新请求其资产大小的图像。

有人能告诉我如何解决这个问题吗?https://stackoverflow.com/questions/55488998/how-to-fix-empty-asset-issue-with-phimagemanager-in-assetspickercontroller-ios?我也遇到了视频的同样问题。 - user2786

6

我已经尝试了许多方法:

  • 目标大小大于(400,400):无效
  • 目标大小等于资源大小:无效
  • 在设置中禁用 iClo​​ud 照片中的优化存储 :无效
  • requestImage 发送到后台队列:无效
  • 使用 PHImageManagerMaximumSize :无效
  • 使用 isNetworkAccessAllowed :无效
  • 尝试不同值的 PHImageRequestOptions ,例如 version deliveryMode resizeMode :无效
  • 添加 progressHandler :无效
  • 如果失败,请再次调用 requestImage :无效

我得到的只是nil UIImage 和带有 PHImageResultDeliveredImageFormatKey 的信息,就像在这个radar中一样:Photos Frameworks returns no error or image for particular assets

使用纵横比适应

对我有效果的是,参见https://github.com/hyperoslo/Gallery/blob/master/Sources/Images/Image.swift#L34

  • 使用小于200的 targetSize :这就是为什么我可以加载缩略图的原因
  • 使用 aspectFit :对我来说,指定 contentMode 就行了

以下是代码:

let options = PHImageRequestOptions()
options.isSynchronous = true
options.isNetworkAccessAllowed = true

var result: UIImage? = nil

PHImageManager.default().requestImage(
  for: asset,
  targetSize: size,
  contentMode: .aspectFit,
  options: options) { (image, _) in
    result = image
}

return result

异步获取数据

上述操作可能会导致竞争条件,因此请确保您进行异步获取数据,这意味着不使用isSynchronous。请参阅https://github.com/hyperoslo/Gallery/pull/72


请问您能否查看一下这个链接 https://stackoverflow.com/questions/55488998/how-to-fix-empty-asset-issue-with-phimagemanager-in-assetspickercontroller-ios? 我也遇到了同样的视频空白问题。 - user2786

6

我也遇到过这个问题,唯一让我成功的方法是将options.isSynchronous = false。我的具体选项如下:

options.isNetworkAccessAllowed = true
options.deliveryMode = .highQualityFormat
options.version = .current
options.resizeMode = .exact
options.isSynchronous = false

我目前在测试iPhone 11 Pro,iOS 13时遇到了类似的问题。而options.version = .current帮助了我,谢谢!你救了我的命! - user3788747

2

对我有用的方法是让PHImageManager在异步后台线程同步加载资产数据。简单来说,代码如下:

    DispatchQueue.global(qos: .userInitiated).async {
        let requestOptions = PHImageRequestOptions()
        requestOptions.isNetworkAccessAllowed = true
        requestOptions.version = .current
        requestOptions.deliveryMode = .opportunistic
        requestOptions.isSynchronous = true
        PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFit, options: requestOptions) { image, _ in
            DispatchQueue.main.async { /* do something with the image */ }
        }
    }

2

尝试使用大于(400,400)的targetSize。这对我很有帮助。


有人能告诉我如何解决这个问题吗?https://stackoverflow.com/questions/55488998/how-to-fix-empty-asset-issue-with-phimagemanager-in-assetspickercontroller-ios?我也遇到了视频的同样问题。 - user2786

2

1
以下内容解决了我的问题:
let options = PHImageRequestOptions()
options.isSynchronous = false
options.isNetworkAccessAllowed = true
options.deliveryMode = .opportunistic
options.version = .current
options.resizeMode = .exact

0
我的解决方案是设置一个目标大小(targetSize)
var image: UIImage?
let options = PHImageRequestOptions()
options.isNetworkAccessAllowed = true
options.isSynchronous = true
options.resizeMode = PHImageRequestOptionsResizeMode.exact

let targetSize = CGSize(width:1200, height:1200)

PHImageManager.default().requestImage(for: self, targetSize: targetSize, contentMode: PHImageContentMode.aspectFit, options: options) { (receivedImage, info) in

    if let formAnImage = receivedImage
    {
        image = formAnImage     
    }
}

写代码愉快!


有人能告诉我如何解决这个问题吗?https://stackoverflow.com/questions/55488998/how-to-fix-empty-asset-issue-with-phimagemanager-in-assetspickercontroller-ios?我也遇到了视频的同样问题。 - user2786

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接