Phimagemanager requestigeforasset renvoie zéro parfois pour les photos iCloud
environ 10% du temps PHImageManager.defaultManager ().requestImageForAsset retourne nil au lieu d'un UIImage valide après avoir retourné un UIImage valide bien que "dégradé". Aucune erreur ou autre indice que je peux voir est retourné dans l'info avec le néant.
cela semble se produire avec des photos qui doivent être téléchargées à partir d'iCloud, avec iCloud Photothèque et optimiser le stockage iPad les deux activées. J'ai essayé de changer les options, la taille, etc. mais rien ne semble question.
si je réessaie le requestie pour l'ensemble après l'échec, il sera généralement correctement retourner un UIImage, bien que parfois il nécessite un couple de réessais.
une idée de ce que je pourrais faire de mal? Ou est-ce juste un bug dans le cadre 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)")
}
}
}
7 réponses
je suis juste allé à travers ce aussi. Par mes tests, le problème apparaît sur les appareils qui ont l'option" Optimiser le stockage " activée et réside dans la différence entre les deux méthodes ci-dessous:
[[[PHImageManager defaultManager] requestigeforasset:...]
ceci récupérera avec succès les images iCloud distantes si vos options sont correctement configurées.
[[[PHImageManager defaultManager] requestImageDataForAsset:...]
cette fonction ne fonctionne que pour les images qui résident sur la mémoire des téléphones ou qui ont été récemment récupérées à partir d'iCloud par votre application sur une autre.
Voici un travail extrait de je suis aide -ours avec moi l'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);
}];
Plein de gist disponible sur github
mise à jour pour Swift 3:
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))")
})
j'ai trouvé que cela n'avait rien à voir avec le réseau ou iCloud. Il a parfois échoué, même sur des images qui étaient complètement locales. Parfois c'était des images de mon appareil photo, parfois c'était des images sauvées du web.
Je n'ai pas trouvé de solution, mais un travail autour inspiré par @Nadzeya qui a fonctionné 100% du temps pour moi était toujours demander une cible de taille égale à la taille de l'actif.
par exemple.
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
}
});
je crois que les inconvénients ce serait que nous obtenions l'image complète de nouveau dans la mémoire, et forçant ensuite L'ImageView à faire la mise à l'échelle, mais au moins dans mon cas d'utilisation, il n'y avait pas de problème de performance perceptible, et c'était beaucoup mieux que de charger une image floue ou nulle.
une optimisation possible ici est de requérir l'image à sa taille d'atout seulement si l'image revient comme nulle.
j'ai essayé beaucoup de choses
- targetSize supérieur (400, 400): pas de travail
- targetSize égale à la taille de l'actif: ne fonctionne pas
- Désactiver
Optimize Storage
dans iCloud Photos dans les Paramètres: pas de travail - Envoi
requestImage
à la file d'attente de fond: pas de travail - Utiliser
PHImageManagerMaximumSize
: non - Utiliser
isNetworkAccessAllowed
: non - Jouer avec les différentes valeurs
PHImageRequestOptions
, commeversion
,deliveryMode
,resizeMode
: non - Ajouter un
progressHandler
: non - Appel
requestImage
encore une fois il cas, il a échoué: pas de travail
Tout ce que j'obtiens est nul UIImage
et info PHImageResultDeliveredImageFormatKey
, comme dans ce radar photos Frameworks ne renvoie aucune erreur ou image pour des biens particuliers
utiliser aspect fit
Ce travail pour moi, voir https://github.com/hyperoslo/Gallery/blob/master/Sources/Images/Image.swift#L34
- Utiliser
targetSize
avec < 200: c'est pourquoi je peux charger la miniature - Utiliser
aspectFit
: spécifier àcontentMode
le truc pour moi c'
Voici le code
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
Extraire de manière asynchrone
ce qui précède peut causer une condition de course, alors assurez-vous de récupérer de façon asynchrone, ce qui signifie non isSynchronous
. Jetez un coup d'oeil à https://github.com/hyperoslo/Gallery/pull/72
Essayez d'utiliser targetSize supérieur (400, 400). Il m'a aidé.
ce qui a fonctionné pour moi était de laisser PHImageManager charger les données de l'actif de façon synchrone, mais à partir d'un fil asynchrone de fond. Simplifié, il ressemble à ceci:
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 */ }
}
}
j'avais aussi nil
pour les images iCloud. Cela n'a pas fait de différence si j'ai utilisé requestImage
ou requestImageData
saveur de la méthode. Mon problème, il s'avère, était que mon appareil a été connecté au réseau via Charles Proxy depuis que je voulais surveiller les requêtes et les réponses app made. Pour une raison quelconque, le périphérique ne pourrait pas fonctionner avec iCloud s'il était connecté de cette façon. Une fois que j'aurais désactivé l'Application proxy pourrait obtenir des images iCloud.
la solution pour moi était de mettre un 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
}
}
Bon codage!