如何识别 PHAsset 是否未从 iCloud 完全下载(因此我需要再次请求 options.networkAccessAllowed)

How to identify if a PHAsset is not completely downloaded from iCloud (so I need to request again with options.networkAccessAllowed)

文档说:

PHImageResultIsInCloudKey: A Boolean value indicating whether the photo asset data is stored on the local device or must be downloaded from iCloud. (NSNumber) If YES, no image was provided, because the asset data must be downloaded from iCloud. To download the data, submit another request, and specify YES for the networkAccessAllowed option.

但是,当资产存储在 iCloud 照片库中时,即使它已经完全下载到设备(在我的应用程序中下载,也在照片应用程序中打开),此密钥始终为 YES。

如果图片不可用,我想为用户提供下载它的可能性(但不要自动下载,至少在没有 Wifi 时不会)。

那么如何判断图片是否需要下载呢?

更好奇:当我的结果块 requestImageForAsset:targetSize:contentMode:options:resultHandler: 为需要下载的图像调用,在交付更小且降级的版本后,我收到最后一次调用 requestedImage == nil。

在这种情况下,降级是 NO,即使我没有图像,图像仍然必须从 iCloud 下载,因为目前只有照片应用程序的小缩略图在本地可用。

我在不同 iOS8 版本(8.1.x、8.2 beta、8.3 beta)的 iPhone 和 iPad 上进行了测试,行为始终相同。

我在照片应用程序中打开图像后,结果处理程序的最后一次调用具有全尺寸图像,但 PHImageResultIsInCloudKey 仍为 YES。

这是我如何请求图像的一些代码:

PHImageRequestOptions *options = [[PHImageRequestOptions alloc]init];
options.deliveryMode = PHImageRequestOptionsDeliveryModeOpportunistic;
options.networkAccessAllowed = NO;

[self.imageManager requestImageForAsset:asset targetSize:size contentMode:PHImageContentModeAspectFill options:options resultHandler:^(UIImage *requestedImage, NSDictionary *info) {
    // Checking for requestedImage and the info keys here
    // When a full sized image was loaded, the result of PHImageResultIsInCloudKey is still YES
    // When a full sized image couldn't be loaded cause it's in the cloud, isDegraded is NO and PHImageResultIsInCloudKey is YES (as always) and requestedImage is nil
}];

我可以确认 PHImageResultIsInCloudKey 不可靠。对于存储在 iCloud 中的图像,它 returns 1,即使原始图像已下载到设备。此行为与文档相反,我建议在 radar.apple.com 报告错误。 PhotoKit 在我看来仍然是一个非常不成熟的框架——它包含很多问题和一些奇怪的概念决定。

如果您调用 requestImageDataForAsset: 并将 networkAccessAllowed 设置为 NO,如果剪辑是 returned imageData 将是 nil尚未从 iCloud 下载。否则它将 return 实际数据,即使剪辑存储在 iCloud 中。

PHImageManager *manager = [PHImageManager defaultManager];    
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.networkAccessAllowed = NO;
[manager
 requestImageDataForAsset:asset
 options:options
 resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
    if ([[info valueForKey:PHImageResultIsInCloudKey] boolValue]) {
    // Image is in iCloud
        if (imageData) {
            // Image is downloaded
        } else {
            // Image is not downloaded
        }
    }
 }];

您可以使用 progressHandler 检查 PHAsset 是否来自 iCloud。

__block BOOL isPhotoInICloud = NO;
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info){

    isPhotoInICloud = YES;
    // some code to update the download progress
});
options.networkAccessAllowed = YES;
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
options.synchronous = NO;

[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
// use the options to get the high quality image for only once time.
});

检查 PHAsset 是否仍在云中的最佳方法是尝试通过 PHImageManager 使用 networkAccessAllowed = NO 访问它,如果您没有得到 image/asset,那么您知道它仍然存在在云端(不会下载):

    + (void)checkCloudStatusForPHAsset:(PHAsset*)phAsset completion:(void (^)(BOOL isInCloud))completionBlock
{
    if (phAsset) {
        if (phAsset.mediaType == PHAssetMediaTypeVideo) {
            PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
            options.version = PHVideoRequestOptionsVersionOriginal;
            options.deliveryMode = PHVideoRequestOptionsDeliveryModeHighQualityFormat;
            options.networkAccessAllowed = NO;
            [[PHImageManager defaultManager] requestAVAssetForVideo:phAsset options:options resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
                completionBlock(asset == nil);
            }];
        }
        else if (phAsset.mediaType == PHAssetMediaTypeImage) {
            PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
            options.version = PHImageRequestOptionsVersionOriginal;
            options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
            options.resizeMode = PHImageRequestOptionsResizeModeNone;
            options.networkAccessAllowed = NO;
            [[PHImageManager defaultManager] requestImageDataForAsset:phAsset options:options resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
                completionBlock(imageData == nil);
            }];
        }
        else {
            completionBlock(NO);
        }
    }
    else {
        completionBlock(NO);
    }
}

以防万一有人在尝试解决 iOS 13.

中的错误时偶然发现这个问题

此线程中的方法自 iOS 8.

以来已被弃用

在 iOS 13 中,您需要改为调用此方法:

- (PHImageRequestID)requestImageDataAndOrientationForAsset:(PHAsset *)asset 
                                                   options:(PHImageRequestOptions *)options 
                                             resultHandler:(void (^)(NSData *imageData, NSString *dataUTI, CGImagePropertyOrientation orientation, NSDictionary *info))resultHandler;

https://developer.apple.com/documentation/photokit/phimagemanager/3237282-requestimagedataandorientationfo