58

大约 10% 的时间 PHImageManager.defaultManager().requestImageForAsset 在第一次返回有效但“降级”的 UIImage 后返回 nil 而不是有效的 UIImage。没有错误或我能看到的其他线索在带有 nil 的信息中返回。

这似乎发生在需要从 iCloud 下载的照片上,同时启用了 iCloud 照片库和优化 iPad 存储。我尝试过更改选项、大小等,但似乎没有什么关系。

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

知道我可能做错了什么吗?或者它只是照片框架中的一个错误?

    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)")
           }
    }
}
4

11 回答 11

52

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

[[PHImageManager defaultManager] requestImageForAsset: ...]

如果您的选项配置正确,这将成功获取远程 iCloud 图像。


[[PHImageManager defaultManager] requestImageDataForAsset:...]

此功能仅适用于驻留在手机内存中的图像或您的应用程序最近从 iCloud 获取的任何其他图像。


这是我正在使用的工作片段 -bear 和 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))")
    })
于 2015-08-03T10:23:58.940 回答
7

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.

于 2017-03-18T05:57:11.283 回答
6

我已经尝试了很多东西

  • targetSize 大于 (400, 400):不起作用
  • targetSize 等于资产大小:不起作用
  • 在设置中的 iCloud 照片中禁用Optimize Storage:不起作用
  • 调度requestImage到后台队列:不工作
  • 使用PHImageManagerMaximumSize:不工作
  • 使用isNetworkAccessAllowed:不工作
  • PHImageRequestOptions在, version, : not workdeliveryMode中使用不同的值resizeMode
  • 添加一个progressHandler:不起作用
  • 再次调用requestImage它失败的情况:不起作用

我得到的只是零UIImage和信息PHImageResultDeliveredImageFormatKey,就像在这个雷达照片框架中返回的特定资产没有错误或图像

使用方面拟合

什么对我有用,请参阅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

异步获取

以上可能会导致竞争条件,因此请确保您异步获取,这意味着 no isSynchronous。看看https://github.com/hyperoslo/Gallery/pull/72

于 2017-11-27T12:49:50.807 回答
6

我也看到了这一点,唯一对我有用的是设置options.isSynchronous = false。我的特别选择是:

options.isNetworkAccessAllowed = true
options.deliveryMode = .highQualityFormat
options.version = .current
options.resizeMode = .exact
options.isSynchronous = false
于 2019-01-15T20:50:34.880 回答
4

以上没有对我有用,但是这个解决方案确实有效!

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
}

这里的区别是递归重试。这对我有用 100% 的时间。

于 2020-03-10T10:38:21.860 回答
2

尝试使用大于 (400, 400) 的 targetSize。它帮助了我。

于 2016-06-30T12:48:20.337 回答
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 */ }
        }
    }
于 2017-08-22T10:08:40.443 回答
1

以下解决了我的问题:

let options = PHImageRequestOptions()
options.isSynchronous = false
options.isNetworkAccessAllowed = true
options.deliveryMode = .opportunistic
options.version = .current
options.resizeMode = .exact
于 2019-11-18T22:24:49.237 回答
0

从 iOS 14 开始,如果用户未授予使用受限照片选择器使用该特定照片的权限,也会发生这种情况。欲了解更多信息,https://developer.apple.com/documentation/photokit/delivering_a_great_privacy_experience_in_your_photos_app

于 2021-05-23T20:27:50.323 回答
0

我也得到nil了 iCloud 图像。如果我使用该方法requestImagerequestImageData该方法的味道并没有什么不同。事实证明,我的问题是我的设备通过 Charles Proxy 连接到网络,因为我想监控应用程序发出的请求和响应。出于某种原因,如果以这种方式连接,设备将无法与 iCloud 一起使用。一旦我关闭代理应用程序就可以获得 iCloud 图像。

于 2018-03-20T00:05:57.483 回答
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     
    }
}

好编码!

于 2018-08-07T14:23:15.463 回答