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)")
}
}
}
私もこれを経験しました。私のテストでは、「ストレージの最適化」オプションが有効になっているデバイスで問題が発生し、以下の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);
}];
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))")
})
これはネットワークや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として戻ってきた場合にのみ、そのアセットサイズで画像を再要求することです。
私は多くのことを試しました
Optimize Storage
を無効にします:動作しませんrequestImage
をバックグラウンドキューにディスパッチします:動作しませんPHImageManagerMaximumSize
を使用:動作しませんisNetworkAccessAllowed
を使用:動作しませんPHImageRequestOptions
、version
、deliveryMode
のようなresizeMode
の異なる値でプレイ:動作しませんprogressHandler
を追加:動作しませんrequestImage
を再度呼び出します:動作しませんこのレーダーのように、nil UIImage
とPHImageResultDeliveredImageFormatKey
を含む情報のみが取得されます 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 をご覧ください
(400、400)より大きいtargetSizeを使用してください。助けてくれました。
私もこれを見ていました、そして私のために働いた唯一のことはoptions.isSynchronous = false
。私の特定のオプションは次のとおりです。
options.isNetworkAccessAllowed = true
options.deliveryMode = .highQualityFormat
options.version = .current
options.resizeMode = .exact
options.isSynchronous = false
ICloudイメージのnil
も取得していました。メソッドのrequestImage
またはrequestImageData
フレーバーを使用しても違いはありませんでした。私の問題は、アプリが作成したリクエストとレスポンスを監視したいため、Charles Proxyを介してデバイスがネットワークに接続されていたことでした。何らかの理由で、この方法で接続すると、デバイスはiCloudで動作しませんでした。プロキシアプリをオフにすると、iCloudイメージを取得できます。
私にとっての解決策は、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
}
}
良いコーディング!
私にとってうまくいったのは、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 */ }
}
}