web-dev-qa-db-ja.com

PHImageManager requestImageForAssetは、iCloudの写真に対してnilを時々返す

PHImageManager.defaultManager()。requestImageForAssetは、有効な「劣化した」UIImageを最初に返した後、有効なUIImageではなくnilを返す時間の約10%です。エラーやその他の手掛かりはありませんが、nilの情報で返されます。

これは、iCloud Photo LibraryとOptimize iPad Storageの両方を有効にして、iCloudからダウンロードする必要がある写真で発生するようです。オプション、サイズなどを変更しようとしましたが、何も重要ではないようです。

失敗後にrequestImageForAssetを再試行すると、通常はUIImageが正しく返されますが、場合によっては2、3回再試行する必要があります。

私が間違っているかもしれないことは何ですか?それとも、Photosフレームワークのバグですか?

    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)")
           }
    }
}
44
LenK

私もこれを経験しました。私のテストでは、「ストレージの最適化」オプションが有効になっているデバイスで問題が発生し、以下の2つの方法の違いにあります。

[[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で利用可能な完全なGist

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))")
    })
39
ahbou

これはネットワークや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として戻ってきた場合にのみ、そのアセットサイズで画像を再要求することです。

5
Jeremy

私は多くのことを試しました

  • (400、400)より大きいtargetSize:動作しません
  • targetSizeがアセットサイズに等しい:動作しません
  • [設定]の[iCloud写真]でOptimize Storageを無効にします:動作しません
  • requestImageをバックグラウンドキューにディスパッチします:動作しません
  • PHImageManagerMaximumSizeを使用:動作しません
  • isNetworkAccessAllowedを使用:動作しません
  • PHImageRequestOptionsversiondeliveryModeのようなresizeModeの異なる値でプレイ:動作しません
  • progressHandlerを追加:動作しません
  • 失敗した場合はrequestImageを再度呼び出します:動作しません

このレーダーのように、nil UIImagePHImageResultDeliveredImageFormatKeyを含む情報のみが取得されます Photos Frameworksはエラーや特定のアセットの画像を返しません

アスペクトフィットを使用

私のために働くもの、 https://github.com/hyperoslo/Gallery/blob/master/Sources/Images/Image.Swift#L34 を参照してください

  • targetSizeを200未満で使用:これがサムネイルをロードできる理由です
  • 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 をご覧ください

3
onmyway133

(400、400)より大きいtargetSizeを使用してください。助けてくれました。

2
Nadzeya

私もこれを見ていました、そして私のために働いた唯一のことはoptions.isSynchronous = false。私の特定のオプションは次のとおりです。

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

ICloudイメージのnilも取得していました。メソッドのrequestImageまたはrequestImageDataフレーバーを使用しても違いはありませんでした。私の問題は、アプリが作成したリクエストとレスポンスを監視したいため、Charles Proxyを介してデバイスがネットワークに接続されていたことでした。何らかの理由で、この方法で接続すると、デバイスはiCloudで動作しませんでした。プロキシアプリをオフにすると、iCloudイメージを取得できます。

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

良いコーディング!

0

私にとってうまくいったのは、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 */ }
        }
    }
0
Fran Pugl