Environ 10% du temps, PHImageManager.defaultManager (). RequestImageForAsset renvoie nil au lieu d'un UIImage valide après le premier retour d'un UIImage valide mais "dégradé". Aucune erreur ou autre indice que je peux voir n'est renvoyé dans l'info avec le nil.
Cela semble se produire avec les photos qui doivent être téléchargées à partir d'iCloud, avec iCloud Photo Library et Optimiser le stockage iPad activés. J'ai essayé de changer les options, la taille, etc., mais rien ne semble avoir d'importance.
Si je réessaie le requestImageForAsset après l'échec, il retournera généralement correctement un UIImage, bien que parfois cela nécessite quelques tentatives.
Une idée de ce que je pourrais faire mal? Ou est-ce juste un bug dans le framework 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)")
}
}
}
Je viens de traverser cela aussi. D'après mes tests, le problème apparaît sur les appareils sur lesquels l'option "Optimiser le stockage" est activée et réside dans la différence entre les deux méthodes ci-dessous:
[[PHImageManager defaultManager] requestImageForAsset: ...]
Cela permettra d'extraire des images iCloud distantes avec succès si vos options sont correctement configurées.
[[PHImageManager defaultManager] requestImageDataForAsset: ...]
Cette fonction ne fonctionne que pour les images résidant dans la mémoire du téléphone ou récemment extraites d'iCloud par votre application.
Voici un extrait de travail que j'utilise -bear 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);
}];
Full Gist disponible sur github
Mise à jour pour 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))")
})
J'ai trouvé que cela n'avait rien à voir avec le réseau ou iCloud. Cela a parfois échoué, même sur des images complètement locales. Parfois, c'étaient des images de mon appareil photo, parfois, des images enregistrées à partir du Web.
Je n'ai pas trouvé de solution, mais un travail inspiré par @Nadzeya qui a fonctionné 100% du temps pour moi a été de demander toujours une taille cible é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 pense que l’inconvénient à cela est que nous récupérons l’image complète en mémoire, puis que nous obligeons ImageView à effectuer la mise à l’échelle, mais au moins dans mon cas d’utilisation, il n’y avait pas de problème de performances notable. beaucoup mieux que de charger une image floue ou nulle.
Une optimisation possible consiste à demander à nouveau l'image à sa taille d'actif uniquement si l'image redevient nulle.
J'ai essayé beaucoup de choses
Optimize Storage
dans iCloud Photos dans les paramètres: ne fonctionne pasrequestImage
vers la file d'attente en arrière-plan: ne fonctionne pasPHImageManagerMaximumSize
: ne fonctionne pasisNetworkAccessAllowed
: ne fonctionne pasPHImageRequestOptions
, comme version
, deliveryMode
, resizeMode
: ne fonctionne pasprogressHandler
: ne fonctionne pasrequestImage
encore une fois s'il a échoué: pas de travailTout ce que je reçois est nul UIImage
et info avec PHImageResultDeliveredImageFormatKey
, comme dans ce radar Photos Frameworks ne renvoie aucune erreur ni image pour des éléments particuliers
Utiliser les ajustements d'aspect
Ce qui fonctionne pour moi, voir https://github.com/hyperoslo/Gallery/blob/master/Sources/Images/Image.Swift#L34
targetSize
avec <200: c’est pourquoi je peux charger la vignetteaspectFit
: Spécifier à contentMode
fait le tour pour moiVoici 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
Fetch asynchrone
Ce qui précède peut être à l'origine d'une situation critique, assurez-vous donc que vous effectuez une récupération asynchrone, ce qui signifie que vous n'avez pas isSynchronous
. Jetez un oeil à https://github.com/hyperoslo/Gallery/pull/72
Essayez d’utiliser targetSize supérieur à (400, 400) .
Je le voyais aussi, et la seule chose qui fonctionnait pour moi était de régler options.isSynchronous = false
. Mes options particulières sont:
options.isNetworkAccessAllowed = true
options.deliveryMode = .highQualityFormat
options.version = .current
options.resizeMode = .exact
options.isSynchronous = false
J'obtenais aussi nil
pour les images iCloud. Cela ne faisait aucune différence si j'utilisais la variante requestImage
ou requestImageData
de la méthode. Il se trouve que mon problème était que mon appareil était connecté au réseau via Charles Proxy car je voulais surveiller les demandes et les réponses de l'application. 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'ai désactivé l'application proxy pourrait obtenir des images iCloud.
La solution pour moi était de définir 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!
Ce qui a fonctionné pour moi a été de laisser PHImageManager charger les données d'actif de manière synchrone, mais à partir d'un thread en arrière-plan asynchrone. 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 */ }
}
}